summaryrefslogtreecommitdiffstats
path: root/dom/downloads/tests/serve_file.sjs
diff options
context:
space:
mode:
Diffstat (limited to 'dom/downloads/tests/serve_file.sjs')
-rw-r--r--dom/downloads/tests/serve_file.sjs170
1 files changed, 170 insertions, 0 deletions
diff --git a/dom/downloads/tests/serve_file.sjs b/dom/downloads/tests/serve_file.sjs
new file mode 100644
index 000000000..d0171d7ca
--- /dev/null
+++ b/dom/downloads/tests/serve_file.sjs
@@ -0,0 +1,170 @@
+// Serves a file with a given mime type and size at an optionally given rate.
+
+function getQuery(request) {
+ var query = {};
+ request.queryString.split('&').forEach(function (val) {
+ var [name, value] = val.split('=');
+ query[name] = unescape(value);
+ });
+ return query;
+}
+
+function handleResponse() {
+ // Is this a rate limited response?
+ if (this.state.rate > 0) {
+ // Calculate how many bytes we have left to send.
+ var bytesToWrite = this.state.totalBytes - this.state.sentBytes;
+
+ // Do we have any bytes left to send? If not we'll just fall thru and
+ // cancel our repeating timer and finalize the response.
+ if (bytesToWrite > 0) {
+ // Figure out how many bytes to send, based on the rate limit.
+ bytesToWrite =
+ (bytesToWrite > this.state.rate) ? this.state.rate : bytesToWrite;
+
+ for (let i = 0; i < bytesToWrite; i++) {
+ try {
+ this.response.bodyOutputStream.write("0", 1);
+ } catch (e) {
+ // Connection was closed by client.
+ if (e == Components.results.NS_ERROR_NOT_AVAILABLE) {
+ // There's no harm in calling this multiple times.
+ this.response.finish();
+
+ // It's possible that our timer wasn't cancelled in time
+ // and we'll be called again.
+ if (this.timer) {
+ this.timer.cancel();
+ this.timer = null;
+ }
+
+ return;
+ }
+ }
+ }
+
+ // Update the number of bytes we've sent to the client.
+ this.state.sentBytes += bytesToWrite;
+
+ // Wait until the next call to do anything else.
+ return;
+ }
+ }
+ else {
+ // Not rate limited, write it all out.
+ for (let i = 0; i < this.state.totalBytes; i++) {
+ this.response.write("0");
+ }
+ }
+
+ // Finalize the response.
+ this.response.finish();
+
+ // All done sending, go ahead and cancel our repeating timer.
+ this.timer.cancel();
+
+ // Clear the timer.
+ this.timer = null;
+}
+
+function handleRequest(request, response) {
+ var query = getQuery(request);
+
+ // sending at a specific rate requires our response to be asynchronous so
+ // we handle all requests asynchronously. See handleResponse().
+ response.processAsync();
+
+ // Default status when responding.
+ var version = "1.1";
+ var statusCode = 200;
+ var description = "OK";
+
+ // Default values for content type, size and rate.
+ var contentType = "text/plain";
+ var contentRange = null;
+ var size = 1024;
+ var rate = 0;
+
+ // optional content type to be used by our response.
+ if ("contentType" in query) {
+ contentType = query["contentType"];
+ }
+
+ // optional size (in bytes) for generated file.
+ if ("size" in query) {
+ size = parseInt(query["size"]);
+ }
+
+ // optional range request check.
+ if (request.hasHeader("range")) {
+ version = "1.1";
+ statusCode = 206;
+ description = "Partial Content";
+
+ // We'll only support simple range byte style requests.
+ var [offset, total] = request.getHeader("range").slice("bytes=".length).split("-");
+ // Enforce valid Number values.
+ offset = parseInt(offset);
+ offset = isNaN(offset) ? 0 : offset;
+ // Same.
+ total = parseInt(total);
+ total = isNaN(total) ? 0 : total;
+
+ // We'll need to original total size as part of the Content-Range header
+ // value in our response.
+ var originalSize = size;
+
+ // If we have a total size requested, we must make sure to send that number
+ // of bytes only (minus the start offset).
+ if (total && total < size) {
+ size = total - offset;
+ } else if (offset) {
+ // Looks like we just have a byte offset to deal with.
+ size = size - offset;
+ }
+
+ // We specifically need to add a Content-Range header to all responses for
+ // requests that include a range request header.
+ contentRange = "bytes " + offset + "-" + (size - 1) + "/" + originalSize;
+ }
+
+ // optional rate (in bytes/s) at which to send the file.
+ if ("rate" in query) {
+ rate = parseInt(query["rate"]);
+ }
+
+ // The context for the responseHandler.
+ var context = {
+ response: response,
+ state: {
+ contentType: contentType,
+ totalBytes: size,
+ sentBytes: 0,
+ rate: rate
+ },
+ timer: null
+ };
+
+ // The notify implementation for the timer.
+ context.notify = handleResponse.bind(context);
+
+ context.timer =
+ Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+
+ // generate the content.
+ response.setStatusLine(version, statusCode, description);
+ response.setHeader("Content-Type", contentType, false);
+ if (contentRange) {
+ response.setHeader("Content-Range", contentRange, false);
+ }
+ response.setHeader("Content-Length", size.toString(), false);
+
+ // initialize the timer and start writing out the response.
+ context.timer.initWithCallback(
+ context,
+ 1000,
+ Components.interfaces.nsITimer.TYPE_REPEATING_SLACK
+ );
+
+}