|
1 // test HTTP/2 |
|
2 |
|
3 var Ci = Components.interfaces; |
|
4 var Cc = Components.classes; |
|
5 |
|
6 // Generate a small and a large post with known pre-calculated md5 sums |
|
7 function generateContent(size) { |
|
8 var content = ""; |
|
9 for (var i = 0; i < size; i++) { |
|
10 content += "0"; |
|
11 } |
|
12 return content; |
|
13 } |
|
14 |
|
15 var posts = []; |
|
16 posts.push(generateContent(10)); |
|
17 posts.push(generateContent(250000)); |
|
18 |
|
19 // pre-calculated md5sums (in hex) of the above posts |
|
20 var md5s = ['f1b708bba17f1ce948dc979f4d7092bc', |
|
21 '2ef8d3b6c8f329318eb1a119b12622b6']; |
|
22 |
|
23 var bigListenerData = generateContent(128 * 1024); |
|
24 var bigListenerMD5 = '8f607cfdd2c87d6a7eedb657dafbd836'; |
|
25 |
|
26 function checkIsHttp2(request) { |
|
27 try { |
|
28 if (request.getResponseHeader("X-Firefox-Spdy") == "h2-10") { |
|
29 if (request.getResponseHeader("X-Connection-Http2") == "yes") { |
|
30 return true; |
|
31 } |
|
32 return false; // Weird case, but the server disagrees with us |
|
33 } |
|
34 } catch (e) { |
|
35 // Nothing to do here |
|
36 } |
|
37 return false; |
|
38 } |
|
39 |
|
40 var Http2CheckListener = function() {}; |
|
41 |
|
42 Http2CheckListener.prototype = { |
|
43 onStartRequestFired: false, |
|
44 onDataAvailableFired: false, |
|
45 isHttp2Connection: false, |
|
46 |
|
47 onStartRequest: function testOnStartRequest(request, ctx) { |
|
48 this.onStartRequestFired = true; |
|
49 |
|
50 if (!Components.isSuccessCode(request.status)) |
|
51 do_throw("Channel should have a success code! (" + request.status + ")"); |
|
52 if (!(request instanceof Components.interfaces.nsIHttpChannel)) |
|
53 do_throw("Expecting an HTTP channel"); |
|
54 |
|
55 do_check_eq(request.responseStatus, 200); |
|
56 do_check_eq(request.requestSucceeded, true); |
|
57 }, |
|
58 |
|
59 onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) { |
|
60 this.onDataAvailableFired = true; |
|
61 this.isHttp2Connection = checkIsHttp2(request); |
|
62 |
|
63 read_stream(stream, cnt); |
|
64 }, |
|
65 |
|
66 onStopRequest: function testOnStopRequest(request, ctx, status) { |
|
67 do_check_true(this.onStartRequestFired); |
|
68 do_check_true(this.onDataAvailableFired); |
|
69 do_check_true(this.isHttp2Connection); |
|
70 |
|
71 run_next_test(); |
|
72 do_test_finished(); |
|
73 } |
|
74 }; |
|
75 |
|
76 /* |
|
77 * Support for testing valid multiplexing of streams |
|
78 */ |
|
79 |
|
80 var multiplexContent = generateContent(30*1024); |
|
81 var completed_channels = []; |
|
82 function register_completed_channel(listener) { |
|
83 completed_channels.push(listener); |
|
84 if (completed_channels.length == 2) { |
|
85 do_check_neq(completed_channels[0].streamID, completed_channels[1].streamID); |
|
86 run_next_test(); |
|
87 do_test_finished(); |
|
88 } |
|
89 } |
|
90 |
|
91 /* Listener class to control the testing of multiplexing */ |
|
92 var Http2MultiplexListener = function() {}; |
|
93 |
|
94 Http2MultiplexListener.prototype = new Http2CheckListener(); |
|
95 |
|
96 Http2MultiplexListener.prototype.streamID = 0; |
|
97 Http2MultiplexListener.prototype.buffer = ""; |
|
98 |
|
99 Http2MultiplexListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { |
|
100 this.onDataAvailableFired = true; |
|
101 this.isHttp2Connection = checkIsHttp2(request); |
|
102 this.streamID = parseInt(request.getResponseHeader("X-Http2-StreamID")); |
|
103 var data = read_stream(stream, cnt); |
|
104 this.buffer = this.buffer.concat(data); |
|
105 }; |
|
106 |
|
107 Http2MultiplexListener.prototype.onStopRequest = function(request, ctx, status) { |
|
108 do_check_true(this.onStartRequestFired); |
|
109 do_check_true(this.onDataAvailableFired); |
|
110 do_check_true(this.isHttp2Connection); |
|
111 do_check_true(this.buffer == multiplexContent); |
|
112 |
|
113 // This is what does most of the hard work for us |
|
114 register_completed_channel(this); |
|
115 }; |
|
116 |
|
117 // Does the appropriate checks for header gatewaying |
|
118 var Http2HeaderListener = function(name, callback) { |
|
119 this.name = name; |
|
120 this.callback = callback; |
|
121 }; |
|
122 |
|
123 Http2HeaderListener.prototype = new Http2CheckListener(); |
|
124 Http2HeaderListener.prototype.value = ""; |
|
125 |
|
126 Http2HeaderListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { |
|
127 this.onDataAvailableFired = true; |
|
128 this.isHttp2Connection = checkIsHttp2(request); |
|
129 var hvalue = request.getResponseHeader(this.name); |
|
130 do_check_neq(hvalue, ""); |
|
131 this.callback(hvalue); |
|
132 read_stream(stream, cnt); |
|
133 }; |
|
134 |
|
135 var Http2PushListener = function() {}; |
|
136 |
|
137 Http2PushListener.prototype = new Http2CheckListener(); |
|
138 |
|
139 Http2PushListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { |
|
140 this.onDataAvailableFired = true; |
|
141 this.isHttp2Connection = checkIsHttp2(request); |
|
142 if (ctx.originalURI.spec == "https://localhost:6944/push.js" || |
|
143 ctx.originalURI.spec == "https://localhost:6944/push2.js") { |
|
144 do_check_eq(request.getResponseHeader("pushed"), "yes"); |
|
145 } |
|
146 read_stream(stream, cnt); |
|
147 }; |
|
148 |
|
149 // Does the appropriate checks for a large GET response |
|
150 var Http2BigListener = function() {}; |
|
151 |
|
152 Http2BigListener.prototype = new Http2CheckListener(); |
|
153 Http2BigListener.prototype.buffer = ""; |
|
154 |
|
155 Http2BigListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { |
|
156 this.onDataAvailableFired = true; |
|
157 this.isHttp2Connection = checkIsHttp2(request); |
|
158 this.buffer = this.buffer.concat(read_stream(stream, cnt)); |
|
159 // We know the server should send us the same data as our big post will be, |
|
160 // so the md5 should be the same |
|
161 do_check_eq(bigListenerMD5, request.getResponseHeader("X-Expected-MD5")); |
|
162 }; |
|
163 |
|
164 Http2BigListener.prototype.onStopRequest = function(request, ctx, status) { |
|
165 do_check_true(this.onStartRequestFired); |
|
166 do_check_true(this.onDataAvailableFired); |
|
167 do_check_true(this.isHttp2Connection); |
|
168 |
|
169 // Don't want to flood output, so don't use do_check_eq |
|
170 do_check_true(this.buffer == bigListenerData); |
|
171 |
|
172 run_next_test(); |
|
173 do_test_finished(); |
|
174 }; |
|
175 |
|
176 // Does the appropriate checks for POSTs |
|
177 var Http2PostListener = function(expected_md5) { |
|
178 this.expected_md5 = expected_md5; |
|
179 }; |
|
180 |
|
181 Http2PostListener.prototype = new Http2CheckListener(); |
|
182 Http2PostListener.prototype.expected_md5 = ""; |
|
183 |
|
184 Http2PostListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { |
|
185 this.onDataAvailableFired = true; |
|
186 this.isHttp2Connection = checkIsHttp2(request); |
|
187 read_stream(stream, cnt); |
|
188 do_check_eq(this.expected_md5, request.getResponseHeader("X-Calculated-MD5")); |
|
189 }; |
|
190 |
|
191 function makeChan(url) { |
|
192 var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); |
|
193 var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel); |
|
194 |
|
195 return chan; |
|
196 } |
|
197 |
|
198 // Make sure we make a HTTP2 connection and both us and the server mark it as such |
|
199 function test_http2_basic() { |
|
200 var chan = makeChan("https://localhost:6944/"); |
|
201 var listener = new Http2CheckListener(); |
|
202 chan.asyncOpen(listener, null); |
|
203 } |
|
204 |
|
205 // Support for making sure XHR works over SPDY |
|
206 function checkXhr(xhr) { |
|
207 if (xhr.readyState != 4) { |
|
208 return; |
|
209 } |
|
210 |
|
211 do_check_eq(xhr.status, 200); |
|
212 do_check_eq(checkIsHttp2(xhr), true); |
|
213 run_next_test(); |
|
214 do_test_finished(); |
|
215 } |
|
216 |
|
217 // Fires off an XHR request over SPDY |
|
218 function test_http2_xhr() { |
|
219 var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] |
|
220 .createInstance(Ci.nsIXMLHttpRequest); |
|
221 req.open("GET", "https://localhost:6944/", true); |
|
222 req.addEventListener("readystatechange", function (evt) { checkXhr(req); }, |
|
223 false); |
|
224 req.send(null); |
|
225 } |
|
226 |
|
227 // Test to make sure we get multiplexing right |
|
228 function test_http2_multiplex() { |
|
229 var chan1 = makeChan("https://localhost:6944/multiplex1"); |
|
230 var chan2 = makeChan("https://localhost:6944/multiplex2"); |
|
231 var listener1 = new Http2MultiplexListener(); |
|
232 var listener2 = new Http2MultiplexListener(); |
|
233 chan1.asyncOpen(listener1, null); |
|
234 chan2.asyncOpen(listener2, null); |
|
235 } |
|
236 |
|
237 // Test to make sure we gateway non-standard headers properly |
|
238 function test_http2_header() { |
|
239 var chan = makeChan("https://localhost:6944/header"); |
|
240 var hvalue = "Headers are fun"; |
|
241 chan.setRequestHeader("X-Test-Header", hvalue, false); |
|
242 var listener = new Http2HeaderListener("X-Received-Test-Header", function(received_hvalue) { |
|
243 do_check_eq(received_hvalue, hvalue); |
|
244 }); |
|
245 chan.asyncOpen(listener, null); |
|
246 } |
|
247 |
|
248 // Test to make sure cookies are split into separate fields before compression |
|
249 function test_http2_cookie_crumbling() { |
|
250 var chan = makeChan("https://localhost:6944/cookie_crumbling"); |
|
251 var cookiesSent = ['a=b', 'c=d', 'e=f'].sort(); |
|
252 chan.setRequestHeader("Cookie", cookiesSent.join('; '), false); |
|
253 var listener = new Http2HeaderListener("X-Received-Header-Pairs", function(pairsReceived) { |
|
254 var cookiesReceived = JSON.parse(pairsReceived).filter(function(pair) { |
|
255 return pair[0] == 'cookie'; |
|
256 }).map(function(pair) { |
|
257 return pair[1]; |
|
258 }).sort(); |
|
259 do_check_eq(cookiesReceived.length, cookiesSent.length); |
|
260 cookiesReceived.forEach(function(cookieReceived, index) { |
|
261 do_check_eq(cookiesSent[index], cookieReceived) |
|
262 }); |
|
263 }); |
|
264 chan.asyncOpen(listener, null); |
|
265 } |
|
266 |
|
267 function test_http2_push1() { |
|
268 var chan = makeChan("https://localhost:6944/push"); |
|
269 chan.loadGroup = loadGroup; |
|
270 var listener = new Http2PushListener(); |
|
271 chan.asyncOpen(listener, chan); |
|
272 } |
|
273 |
|
274 function test_http2_push2() { |
|
275 var chan = makeChan("https://localhost:6944/push.js"); |
|
276 chan.loadGroup = loadGroup; |
|
277 var listener = new Http2PushListener(); |
|
278 chan.asyncOpen(listener, chan); |
|
279 } |
|
280 |
|
281 function test_http2_push3() { |
|
282 var chan = makeChan("https://localhost:6944/push2"); |
|
283 chan.loadGroup = loadGroup; |
|
284 var listener = new Http2PushListener(); |
|
285 chan.asyncOpen(listener, chan); |
|
286 } |
|
287 |
|
288 function test_http2_push4() { |
|
289 var chan = makeChan("https://localhost:6944/push2.js"); |
|
290 chan.loadGroup = loadGroup; |
|
291 var listener = new Http2PushListener(); |
|
292 chan.asyncOpen(listener, chan); |
|
293 } |
|
294 |
|
295 // Make sure we handle GETs that cover more than 2 frames properly |
|
296 function test_http2_big() { |
|
297 var chan = makeChan("https://localhost:6944/big"); |
|
298 var listener = new Http2BigListener(); |
|
299 chan.asyncOpen(listener, null); |
|
300 } |
|
301 |
|
302 // Support for doing a POST |
|
303 function do_post(content, chan, listener) { |
|
304 var stream = Cc["@mozilla.org/io/string-input-stream;1"] |
|
305 .createInstance(Ci.nsIStringInputStream); |
|
306 stream.data = content; |
|
307 |
|
308 var uchan = chan.QueryInterface(Ci.nsIUploadChannel); |
|
309 uchan.setUploadStream(stream, "text/plain", stream.available()); |
|
310 |
|
311 chan.requestMethod = "POST"; |
|
312 |
|
313 chan.asyncOpen(listener, null); |
|
314 } |
|
315 |
|
316 // Make sure we can do a simple POST |
|
317 function test_http2_post() { |
|
318 var chan = makeChan("https://localhost:6944/post"); |
|
319 var listener = new Http2PostListener(md5s[0]); |
|
320 do_post(posts[0], chan, listener); |
|
321 } |
|
322 |
|
323 // Make sure we can do a POST that covers more than 2 frames |
|
324 function test_http2_post_big() { |
|
325 var chan = makeChan("https://localhost:6944/post"); |
|
326 var listener = new Http2PostListener(md5s[1]); |
|
327 do_post(posts[1], chan, listener); |
|
328 } |
|
329 |
|
330 // hack - the header test resets the multiplex object on the server, |
|
331 // so make sure header is always run before the multiplex test. |
|
332 // |
|
333 // make sure post_big runs first to test race condition in restarting |
|
334 // a stalled stream when a SETTINGS frame arrives |
|
335 var tests = [ test_http2_post_big |
|
336 , test_http2_basic |
|
337 , test_http2_push1 |
|
338 , test_http2_push2 |
|
339 , test_http2_push3 |
|
340 , test_http2_push4 |
|
341 , test_http2_xhr |
|
342 , test_http2_header |
|
343 , test_http2_cookie_crumbling |
|
344 , test_http2_multiplex |
|
345 , test_http2_big |
|
346 , test_http2_post |
|
347 ]; |
|
348 var current_test = 0; |
|
349 |
|
350 function run_next_test() { |
|
351 if (current_test < tests.length) { |
|
352 tests[current_test](); |
|
353 current_test++; |
|
354 do_test_pending(); |
|
355 } |
|
356 } |
|
357 |
|
358 // Support for making sure we can talk to the invalid cert the server presents |
|
359 var CertOverrideListener = function(host, port, bits) { |
|
360 this.host = host; |
|
361 if (port) { |
|
362 this.port = port; |
|
363 } |
|
364 this.bits = bits; |
|
365 }; |
|
366 |
|
367 CertOverrideListener.prototype = { |
|
368 host: null, |
|
369 port: -1, |
|
370 bits: null, |
|
371 |
|
372 getInterface: function(aIID) { |
|
373 return this.QueryInterface(aIID); |
|
374 }, |
|
375 |
|
376 QueryInterface: function(aIID) { |
|
377 if (aIID.equals(Ci.nsIBadCertListener2) || |
|
378 aIID.equals(Ci.nsIInterfaceRequestor) || |
|
379 aIID.equals(Ci.nsISupports)) |
|
380 return this; |
|
381 throw Components.results.NS_ERROR_NO_INTERFACE; |
|
382 }, |
|
383 |
|
384 notifyCertProblem: function(socketInfo, sslStatus, targetHost) { |
|
385 var cert = sslStatus.QueryInterface(Ci.nsISSLStatus).serverCert; |
|
386 var cos = Cc["@mozilla.org/security/certoverride;1"]. |
|
387 getService(Ci.nsICertOverrideService); |
|
388 cos.rememberValidityOverride(this.host, this.port, cert, this.bits, false); |
|
389 return true; |
|
390 }, |
|
391 }; |
|
392 |
|
393 function addCertOverride(host, port, bits) { |
|
394 var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] |
|
395 .createInstance(Ci.nsIXMLHttpRequest); |
|
396 try { |
|
397 var url; |
|
398 if (port) { |
|
399 url = "https://" + host + ":" + port + "/"; |
|
400 } else { |
|
401 url = "https://" + host + "/"; |
|
402 } |
|
403 req.open("GET", url, false); |
|
404 req.channel.notificationCallbacks = new CertOverrideListener(host, port, bits); |
|
405 req.send(null); |
|
406 } catch (e) { |
|
407 // This will fail since the server is not trusted yet |
|
408 } |
|
409 } |
|
410 |
|
411 var prefs; |
|
412 var spdypref; |
|
413 var spdy3pref; |
|
414 var spdypush; |
|
415 var http2pref; |
|
416 |
|
417 var loadGroup; |
|
418 |
|
419 function resetPrefs() { |
|
420 prefs.setBoolPref("network.http.spdy.enabled", spdypref); |
|
421 prefs.setBoolPref("network.http.spdy.enabled.v3", spdy3pref); |
|
422 prefs.setBoolPref("network.http.spdy.allow-push", spdypush); |
|
423 prefs.setBoolPref("network.http.spdy.enabled.http2draft", http2pref); |
|
424 } |
|
425 |
|
426 function run_test() { |
|
427 // Set to allow the cert presented by our SPDY server |
|
428 do_get_profile(); |
|
429 var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); |
|
430 var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit"); |
|
431 prefs.setIntPref("network.http.speculative-parallel-limit", 0); |
|
432 |
|
433 addCertOverride("localhost", 6944, |
|
434 Ci.nsICertOverrideService.ERROR_UNTRUSTED | |
|
435 Ci.nsICertOverrideService.ERROR_MISMATCH | |
|
436 Ci.nsICertOverrideService.ERROR_TIME); |
|
437 |
|
438 prefs.setIntPref("network.http.speculative-parallel-limit", oldPref); |
|
439 |
|
440 // Enable all versions of spdy to see that we auto negotiate http/2 |
|
441 spdypref = prefs.getBoolPref("network.http.spdy.enabled"); |
|
442 spdy3pref = prefs.getBoolPref("network.http.spdy.enabled.v3"); |
|
443 spdypush = prefs.getBoolPref("network.http.spdy.allow-push"); |
|
444 http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2draft"); |
|
445 prefs.setBoolPref("network.http.spdy.enabled", true); |
|
446 prefs.setBoolPref("network.http.spdy.enabled.v3", true); |
|
447 prefs.setBoolPref("network.http.spdy.allow-push", true); |
|
448 prefs.setBoolPref("network.http.spdy.enabled.http2draft", true); |
|
449 |
|
450 loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup); |
|
451 |
|
452 // And make go! |
|
453 run_next_test(); |
|
454 } |