1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/downloads/tests/serve_file.sjs Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,170 @@ 1.4 +// Serves a file with a given mime type and size at an optionally given rate. 1.5 + 1.6 +function getQuery(request) { 1.7 + var query = {}; 1.8 + request.queryString.split('&').forEach(function (val) { 1.9 + var [name, value] = val.split('='); 1.10 + query[name] = unescape(value); 1.11 + }); 1.12 + return query; 1.13 +} 1.14 + 1.15 +function handleResponse() { 1.16 + // Is this a rate limited response? 1.17 + if (this.state.rate > 0) { 1.18 + // Calculate how many bytes we have left to send. 1.19 + var bytesToWrite = this.state.totalBytes - this.state.sentBytes; 1.20 + 1.21 + // Do we have any bytes left to send? If not we'll just fall thru and 1.22 + // cancel our repeating timer and finalize the response. 1.23 + if (bytesToWrite > 0) { 1.24 + // Figure out how many bytes to send, based on the rate limit. 1.25 + bytesToWrite = 1.26 + (bytesToWrite > this.state.rate) ? this.state.rate : bytesToWrite; 1.27 + 1.28 + for (let i = 0; i < bytesToWrite; i++) { 1.29 + try { 1.30 + this.response.bodyOutputStream.write("0", 1); 1.31 + } catch (e) { 1.32 + // Connection was closed by client. 1.33 + if (e == Components.results.NS_ERROR_NOT_AVAILABLE) { 1.34 + // There's no harm in calling this multiple times. 1.35 + this.response.finish(); 1.36 + 1.37 + // It's possible that our timer wasn't cancelled in time 1.38 + // and we'll be called again. 1.39 + if (this.timer) { 1.40 + this.timer.cancel(); 1.41 + this.timer = null; 1.42 + } 1.43 + 1.44 + return; 1.45 + } 1.46 + } 1.47 + } 1.48 + 1.49 + // Update the number of bytes we've sent to the client. 1.50 + this.state.sentBytes += bytesToWrite; 1.51 + 1.52 + // Wait until the next call to do anything else. 1.53 + return; 1.54 + } 1.55 + } 1.56 + else { 1.57 + // Not rate limited, write it all out. 1.58 + for (let i = 0; i < this.state.totalBytes; i++) { 1.59 + this.response.write("0"); 1.60 + } 1.61 + } 1.62 + 1.63 + // Finalize the response. 1.64 + this.response.finish(); 1.65 + 1.66 + // All done sending, go ahead and cancel our repeating timer. 1.67 + this.timer.cancel(); 1.68 + 1.69 + // Clear the timer. 1.70 + this.timer = null; 1.71 +} 1.72 + 1.73 +function handleRequest(request, response) { 1.74 + var query = getQuery(request); 1.75 + 1.76 + // sending at a specific rate requires our response to be asynchronous so 1.77 + // we handle all requests asynchronously. See handleResponse(). 1.78 + response.processAsync(); 1.79 + 1.80 + // Default status when responding. 1.81 + var version = "1.1"; 1.82 + var statusCode = 200; 1.83 + var description = "OK"; 1.84 + 1.85 + // Default values for content type, size and rate. 1.86 + var contentType = "text/plain"; 1.87 + var contentRange = null; 1.88 + var size = 1024; 1.89 + var rate = 0; 1.90 + 1.91 + // optional content type to be used by our response. 1.92 + if ("contentType" in query) { 1.93 + contentType = query["contentType"]; 1.94 + } 1.95 + 1.96 + // optional size (in bytes) for generated file. 1.97 + if ("size" in query) { 1.98 + size = parseInt(query["size"]); 1.99 + } 1.100 + 1.101 + // optional range request check. 1.102 + if (request.hasHeader("range")) { 1.103 + version = "1.1"; 1.104 + statusCode = 206; 1.105 + description = "Partial Content"; 1.106 + 1.107 + // We'll only support simple range byte style requests. 1.108 + var [offset, total] = request.getHeader("range").slice("bytes=".length).split("-"); 1.109 + // Enforce valid Number values. 1.110 + offset = parseInt(offset); 1.111 + offset = isNaN(offset) ? 0 : offset; 1.112 + // Same. 1.113 + total = parseInt(total); 1.114 + total = isNaN(total) ? 0 : total; 1.115 + 1.116 + // We'll need to original total size as part of the Content-Range header 1.117 + // value in our response. 1.118 + var originalSize = size; 1.119 + 1.120 + // If we have a total size requested, we must make sure to send that number 1.121 + // of bytes only (minus the start offset). 1.122 + if (total && total < size) { 1.123 + size = total - offset; 1.124 + } else if (offset) { 1.125 + // Looks like we just have a byte offset to deal with. 1.126 + size = size - offset; 1.127 + } 1.128 + 1.129 + // We specifically need to add a Content-Range header to all responses for 1.130 + // requests that include a range request header. 1.131 + contentRange = "bytes " + offset + "-" + (size - 1) + "/" + originalSize; 1.132 + } 1.133 + 1.134 + // optional rate (in bytes/s) at which to send the file. 1.135 + if ("rate" in query) { 1.136 + rate = parseInt(query["rate"]); 1.137 + } 1.138 + 1.139 + // The context for the responseHandler. 1.140 + var context = { 1.141 + response: response, 1.142 + state: { 1.143 + contentType: contentType, 1.144 + totalBytes: size, 1.145 + sentBytes: 0, 1.146 + rate: rate 1.147 + }, 1.148 + timer: null 1.149 + }; 1.150 + 1.151 + // The notify implementation for the timer. 1.152 + context.notify = handleResponse.bind(context); 1.153 + 1.154 + context.timer = 1.155 + Components.classes["@mozilla.org/timer;1"] 1.156 + .createInstance(Components.interfaces.nsITimer); 1.157 + 1.158 + // generate the content. 1.159 + response.setStatusLine(version, statusCode, description); 1.160 + response.setHeader("Content-Type", contentType, false); 1.161 + if (contentRange) { 1.162 + response.setHeader("Content-Range", contentRange, false); 1.163 + } 1.164 + response.setHeader("Content-Length", size.toString(), false); 1.165 + 1.166 + // initialize the timer and start writing out the response. 1.167 + context.timer.initWithCallback( 1.168 + context, 1.169 + 1000, 1.170 + Components.interfaces.nsITimer.TYPE_REPEATING_SLACK 1.171 + ); 1.172 + 1.173 +}