dom/downloads/tests/serve_file.sjs

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 // Serves a file with a given mime type and size at an optionally given rate.
michael@0 2
michael@0 3 function getQuery(request) {
michael@0 4 var query = {};
michael@0 5 request.queryString.split('&').forEach(function (val) {
michael@0 6 var [name, value] = val.split('=');
michael@0 7 query[name] = unescape(value);
michael@0 8 });
michael@0 9 return query;
michael@0 10 }
michael@0 11
michael@0 12 function handleResponse() {
michael@0 13 // Is this a rate limited response?
michael@0 14 if (this.state.rate > 0) {
michael@0 15 // Calculate how many bytes we have left to send.
michael@0 16 var bytesToWrite = this.state.totalBytes - this.state.sentBytes;
michael@0 17
michael@0 18 // Do we have any bytes left to send? If not we'll just fall thru and
michael@0 19 // cancel our repeating timer and finalize the response.
michael@0 20 if (bytesToWrite > 0) {
michael@0 21 // Figure out how many bytes to send, based on the rate limit.
michael@0 22 bytesToWrite =
michael@0 23 (bytesToWrite > this.state.rate) ? this.state.rate : bytesToWrite;
michael@0 24
michael@0 25 for (let i = 0; i < bytesToWrite; i++) {
michael@0 26 try {
michael@0 27 this.response.bodyOutputStream.write("0", 1);
michael@0 28 } catch (e) {
michael@0 29 // Connection was closed by client.
michael@0 30 if (e == Components.results.NS_ERROR_NOT_AVAILABLE) {
michael@0 31 // There's no harm in calling this multiple times.
michael@0 32 this.response.finish();
michael@0 33
michael@0 34 // It's possible that our timer wasn't cancelled in time
michael@0 35 // and we'll be called again.
michael@0 36 if (this.timer) {
michael@0 37 this.timer.cancel();
michael@0 38 this.timer = null;
michael@0 39 }
michael@0 40
michael@0 41 return;
michael@0 42 }
michael@0 43 }
michael@0 44 }
michael@0 45
michael@0 46 // Update the number of bytes we've sent to the client.
michael@0 47 this.state.sentBytes += bytesToWrite;
michael@0 48
michael@0 49 // Wait until the next call to do anything else.
michael@0 50 return;
michael@0 51 }
michael@0 52 }
michael@0 53 else {
michael@0 54 // Not rate limited, write it all out.
michael@0 55 for (let i = 0; i < this.state.totalBytes; i++) {
michael@0 56 this.response.write("0");
michael@0 57 }
michael@0 58 }
michael@0 59
michael@0 60 // Finalize the response.
michael@0 61 this.response.finish();
michael@0 62
michael@0 63 // All done sending, go ahead and cancel our repeating timer.
michael@0 64 this.timer.cancel();
michael@0 65
michael@0 66 // Clear the timer.
michael@0 67 this.timer = null;
michael@0 68 }
michael@0 69
michael@0 70 function handleRequest(request, response) {
michael@0 71 var query = getQuery(request);
michael@0 72
michael@0 73 // sending at a specific rate requires our response to be asynchronous so
michael@0 74 // we handle all requests asynchronously. See handleResponse().
michael@0 75 response.processAsync();
michael@0 76
michael@0 77 // Default status when responding.
michael@0 78 var version = "1.1";
michael@0 79 var statusCode = 200;
michael@0 80 var description = "OK";
michael@0 81
michael@0 82 // Default values for content type, size and rate.
michael@0 83 var contentType = "text/plain";
michael@0 84 var contentRange = null;
michael@0 85 var size = 1024;
michael@0 86 var rate = 0;
michael@0 87
michael@0 88 // optional content type to be used by our response.
michael@0 89 if ("contentType" in query) {
michael@0 90 contentType = query["contentType"];
michael@0 91 }
michael@0 92
michael@0 93 // optional size (in bytes) for generated file.
michael@0 94 if ("size" in query) {
michael@0 95 size = parseInt(query["size"]);
michael@0 96 }
michael@0 97
michael@0 98 // optional range request check.
michael@0 99 if (request.hasHeader("range")) {
michael@0 100 version = "1.1";
michael@0 101 statusCode = 206;
michael@0 102 description = "Partial Content";
michael@0 103
michael@0 104 // We'll only support simple range byte style requests.
michael@0 105 var [offset, total] = request.getHeader("range").slice("bytes=".length).split("-");
michael@0 106 // Enforce valid Number values.
michael@0 107 offset = parseInt(offset);
michael@0 108 offset = isNaN(offset) ? 0 : offset;
michael@0 109 // Same.
michael@0 110 total = parseInt(total);
michael@0 111 total = isNaN(total) ? 0 : total;
michael@0 112
michael@0 113 // We'll need to original total size as part of the Content-Range header
michael@0 114 // value in our response.
michael@0 115 var originalSize = size;
michael@0 116
michael@0 117 // If we have a total size requested, we must make sure to send that number
michael@0 118 // of bytes only (minus the start offset).
michael@0 119 if (total && total < size) {
michael@0 120 size = total - offset;
michael@0 121 } else if (offset) {
michael@0 122 // Looks like we just have a byte offset to deal with.
michael@0 123 size = size - offset;
michael@0 124 }
michael@0 125
michael@0 126 // We specifically need to add a Content-Range header to all responses for
michael@0 127 // requests that include a range request header.
michael@0 128 contentRange = "bytes " + offset + "-" + (size - 1) + "/" + originalSize;
michael@0 129 }
michael@0 130
michael@0 131 // optional rate (in bytes/s) at which to send the file.
michael@0 132 if ("rate" in query) {
michael@0 133 rate = parseInt(query["rate"]);
michael@0 134 }
michael@0 135
michael@0 136 // The context for the responseHandler.
michael@0 137 var context = {
michael@0 138 response: response,
michael@0 139 state: {
michael@0 140 contentType: contentType,
michael@0 141 totalBytes: size,
michael@0 142 sentBytes: 0,
michael@0 143 rate: rate
michael@0 144 },
michael@0 145 timer: null
michael@0 146 };
michael@0 147
michael@0 148 // The notify implementation for the timer.
michael@0 149 context.notify = handleResponse.bind(context);
michael@0 150
michael@0 151 context.timer =
michael@0 152 Components.classes["@mozilla.org/timer;1"]
michael@0 153 .createInstance(Components.interfaces.nsITimer);
michael@0 154
michael@0 155 // generate the content.
michael@0 156 response.setStatusLine(version, statusCode, description);
michael@0 157 response.setHeader("Content-Type", contentType, false);
michael@0 158 if (contentRange) {
michael@0 159 response.setHeader("Content-Range", contentRange, false);
michael@0 160 }
michael@0 161 response.setHeader("Content-Length", size.toString(), false);
michael@0 162
michael@0 163 // initialize the timer and start writing out the response.
michael@0 164 context.timer.initWithCallback(
michael@0 165 context,
michael@0 166 1000,
michael@0 167 Components.interfaces.nsITimer.TYPE_REPEATING_SLACK
michael@0 168 );
michael@0 169
michael@0 170 }

mercurial