dom/downloads/tests/serve_file.sjs

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

mercurial