1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/netwerk/test/unit/test_http2.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,454 @@ 1.4 +// test HTTP/2 1.5 + 1.6 +var Ci = Components.interfaces; 1.7 +var Cc = Components.classes; 1.8 + 1.9 +// Generate a small and a large post with known pre-calculated md5 sums 1.10 +function generateContent(size) { 1.11 + var content = ""; 1.12 + for (var i = 0; i < size; i++) { 1.13 + content += "0"; 1.14 + } 1.15 + return content; 1.16 +} 1.17 + 1.18 +var posts = []; 1.19 +posts.push(generateContent(10)); 1.20 +posts.push(generateContent(250000)); 1.21 + 1.22 +// pre-calculated md5sums (in hex) of the above posts 1.23 +var md5s = ['f1b708bba17f1ce948dc979f4d7092bc', 1.24 + '2ef8d3b6c8f329318eb1a119b12622b6']; 1.25 + 1.26 +var bigListenerData = generateContent(128 * 1024); 1.27 +var bigListenerMD5 = '8f607cfdd2c87d6a7eedb657dafbd836'; 1.28 + 1.29 +function checkIsHttp2(request) { 1.30 + try { 1.31 + if (request.getResponseHeader("X-Firefox-Spdy") == "h2-10") { 1.32 + if (request.getResponseHeader("X-Connection-Http2") == "yes") { 1.33 + return true; 1.34 + } 1.35 + return false; // Weird case, but the server disagrees with us 1.36 + } 1.37 + } catch (e) { 1.38 + // Nothing to do here 1.39 + } 1.40 + return false; 1.41 +} 1.42 + 1.43 +var Http2CheckListener = function() {}; 1.44 + 1.45 +Http2CheckListener.prototype = { 1.46 + onStartRequestFired: false, 1.47 + onDataAvailableFired: false, 1.48 + isHttp2Connection: false, 1.49 + 1.50 + onStartRequest: function testOnStartRequest(request, ctx) { 1.51 + this.onStartRequestFired = true; 1.52 + 1.53 + if (!Components.isSuccessCode(request.status)) 1.54 + do_throw("Channel should have a success code! (" + request.status + ")"); 1.55 + if (!(request instanceof Components.interfaces.nsIHttpChannel)) 1.56 + do_throw("Expecting an HTTP channel"); 1.57 + 1.58 + do_check_eq(request.responseStatus, 200); 1.59 + do_check_eq(request.requestSucceeded, true); 1.60 + }, 1.61 + 1.62 + onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) { 1.63 + this.onDataAvailableFired = true; 1.64 + this.isHttp2Connection = checkIsHttp2(request); 1.65 + 1.66 + read_stream(stream, cnt); 1.67 + }, 1.68 + 1.69 + onStopRequest: function testOnStopRequest(request, ctx, status) { 1.70 + do_check_true(this.onStartRequestFired); 1.71 + do_check_true(this.onDataAvailableFired); 1.72 + do_check_true(this.isHttp2Connection); 1.73 + 1.74 + run_next_test(); 1.75 + do_test_finished(); 1.76 + } 1.77 +}; 1.78 + 1.79 +/* 1.80 + * Support for testing valid multiplexing of streams 1.81 + */ 1.82 + 1.83 +var multiplexContent = generateContent(30*1024); 1.84 +var completed_channels = []; 1.85 +function register_completed_channel(listener) { 1.86 + completed_channels.push(listener); 1.87 + if (completed_channels.length == 2) { 1.88 + do_check_neq(completed_channels[0].streamID, completed_channels[1].streamID); 1.89 + run_next_test(); 1.90 + do_test_finished(); 1.91 + } 1.92 +} 1.93 + 1.94 +/* Listener class to control the testing of multiplexing */ 1.95 +var Http2MultiplexListener = function() {}; 1.96 + 1.97 +Http2MultiplexListener.prototype = new Http2CheckListener(); 1.98 + 1.99 +Http2MultiplexListener.prototype.streamID = 0; 1.100 +Http2MultiplexListener.prototype.buffer = ""; 1.101 + 1.102 +Http2MultiplexListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { 1.103 + this.onDataAvailableFired = true; 1.104 + this.isHttp2Connection = checkIsHttp2(request); 1.105 + this.streamID = parseInt(request.getResponseHeader("X-Http2-StreamID")); 1.106 + var data = read_stream(stream, cnt); 1.107 + this.buffer = this.buffer.concat(data); 1.108 +}; 1.109 + 1.110 +Http2MultiplexListener.prototype.onStopRequest = function(request, ctx, status) { 1.111 + do_check_true(this.onStartRequestFired); 1.112 + do_check_true(this.onDataAvailableFired); 1.113 + do_check_true(this.isHttp2Connection); 1.114 + do_check_true(this.buffer == multiplexContent); 1.115 + 1.116 + // This is what does most of the hard work for us 1.117 + register_completed_channel(this); 1.118 +}; 1.119 + 1.120 +// Does the appropriate checks for header gatewaying 1.121 +var Http2HeaderListener = function(name, callback) { 1.122 + this.name = name; 1.123 + this.callback = callback; 1.124 +}; 1.125 + 1.126 +Http2HeaderListener.prototype = new Http2CheckListener(); 1.127 +Http2HeaderListener.prototype.value = ""; 1.128 + 1.129 +Http2HeaderListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { 1.130 + this.onDataAvailableFired = true; 1.131 + this.isHttp2Connection = checkIsHttp2(request); 1.132 + var hvalue = request.getResponseHeader(this.name); 1.133 + do_check_neq(hvalue, ""); 1.134 + this.callback(hvalue); 1.135 + read_stream(stream, cnt); 1.136 +}; 1.137 + 1.138 +var Http2PushListener = function() {}; 1.139 + 1.140 +Http2PushListener.prototype = new Http2CheckListener(); 1.141 + 1.142 +Http2PushListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { 1.143 + this.onDataAvailableFired = true; 1.144 + this.isHttp2Connection = checkIsHttp2(request); 1.145 + if (ctx.originalURI.spec == "https://localhost:6944/push.js" || 1.146 + ctx.originalURI.spec == "https://localhost:6944/push2.js") { 1.147 + do_check_eq(request.getResponseHeader("pushed"), "yes"); 1.148 + } 1.149 + read_stream(stream, cnt); 1.150 +}; 1.151 + 1.152 +// Does the appropriate checks for a large GET response 1.153 +var Http2BigListener = function() {}; 1.154 + 1.155 +Http2BigListener.prototype = new Http2CheckListener(); 1.156 +Http2BigListener.prototype.buffer = ""; 1.157 + 1.158 +Http2BigListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { 1.159 + this.onDataAvailableFired = true; 1.160 + this.isHttp2Connection = checkIsHttp2(request); 1.161 + this.buffer = this.buffer.concat(read_stream(stream, cnt)); 1.162 + // We know the server should send us the same data as our big post will be, 1.163 + // so the md5 should be the same 1.164 + do_check_eq(bigListenerMD5, request.getResponseHeader("X-Expected-MD5")); 1.165 +}; 1.166 + 1.167 +Http2BigListener.prototype.onStopRequest = function(request, ctx, status) { 1.168 + do_check_true(this.onStartRequestFired); 1.169 + do_check_true(this.onDataAvailableFired); 1.170 + do_check_true(this.isHttp2Connection); 1.171 + 1.172 + // Don't want to flood output, so don't use do_check_eq 1.173 + do_check_true(this.buffer == bigListenerData); 1.174 + 1.175 + run_next_test(); 1.176 + do_test_finished(); 1.177 +}; 1.178 + 1.179 +// Does the appropriate checks for POSTs 1.180 +var Http2PostListener = function(expected_md5) { 1.181 + this.expected_md5 = expected_md5; 1.182 +}; 1.183 + 1.184 +Http2PostListener.prototype = new Http2CheckListener(); 1.185 +Http2PostListener.prototype.expected_md5 = ""; 1.186 + 1.187 +Http2PostListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { 1.188 + this.onDataAvailableFired = true; 1.189 + this.isHttp2Connection = checkIsHttp2(request); 1.190 + read_stream(stream, cnt); 1.191 + do_check_eq(this.expected_md5, request.getResponseHeader("X-Calculated-MD5")); 1.192 +}; 1.193 + 1.194 +function makeChan(url) { 1.195 + var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); 1.196 + var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel); 1.197 + 1.198 + return chan; 1.199 +} 1.200 + 1.201 +// Make sure we make a HTTP2 connection and both us and the server mark it as such 1.202 +function test_http2_basic() { 1.203 + var chan = makeChan("https://localhost:6944/"); 1.204 + var listener = new Http2CheckListener(); 1.205 + chan.asyncOpen(listener, null); 1.206 +} 1.207 + 1.208 +// Support for making sure XHR works over SPDY 1.209 +function checkXhr(xhr) { 1.210 + if (xhr.readyState != 4) { 1.211 + return; 1.212 + } 1.213 + 1.214 + do_check_eq(xhr.status, 200); 1.215 + do_check_eq(checkIsHttp2(xhr), true); 1.216 + run_next_test(); 1.217 + do_test_finished(); 1.218 +} 1.219 + 1.220 +// Fires off an XHR request over SPDY 1.221 +function test_http2_xhr() { 1.222 + var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] 1.223 + .createInstance(Ci.nsIXMLHttpRequest); 1.224 + req.open("GET", "https://localhost:6944/", true); 1.225 + req.addEventListener("readystatechange", function (evt) { checkXhr(req); }, 1.226 + false); 1.227 + req.send(null); 1.228 +} 1.229 + 1.230 +// Test to make sure we get multiplexing right 1.231 +function test_http2_multiplex() { 1.232 + var chan1 = makeChan("https://localhost:6944/multiplex1"); 1.233 + var chan2 = makeChan("https://localhost:6944/multiplex2"); 1.234 + var listener1 = new Http2MultiplexListener(); 1.235 + var listener2 = new Http2MultiplexListener(); 1.236 + chan1.asyncOpen(listener1, null); 1.237 + chan2.asyncOpen(listener2, null); 1.238 +} 1.239 + 1.240 +// Test to make sure we gateway non-standard headers properly 1.241 +function test_http2_header() { 1.242 + var chan = makeChan("https://localhost:6944/header"); 1.243 + var hvalue = "Headers are fun"; 1.244 + chan.setRequestHeader("X-Test-Header", hvalue, false); 1.245 + var listener = new Http2HeaderListener("X-Received-Test-Header", function(received_hvalue) { 1.246 + do_check_eq(received_hvalue, hvalue); 1.247 + }); 1.248 + chan.asyncOpen(listener, null); 1.249 +} 1.250 + 1.251 +// Test to make sure cookies are split into separate fields before compression 1.252 +function test_http2_cookie_crumbling() { 1.253 + var chan = makeChan("https://localhost:6944/cookie_crumbling"); 1.254 + var cookiesSent = ['a=b', 'c=d', 'e=f'].sort(); 1.255 + chan.setRequestHeader("Cookie", cookiesSent.join('; '), false); 1.256 + var listener = new Http2HeaderListener("X-Received-Header-Pairs", function(pairsReceived) { 1.257 + var cookiesReceived = JSON.parse(pairsReceived).filter(function(pair) { 1.258 + return pair[0] == 'cookie'; 1.259 + }).map(function(pair) { 1.260 + return pair[1]; 1.261 + }).sort(); 1.262 + do_check_eq(cookiesReceived.length, cookiesSent.length); 1.263 + cookiesReceived.forEach(function(cookieReceived, index) { 1.264 + do_check_eq(cookiesSent[index], cookieReceived) 1.265 + }); 1.266 + }); 1.267 + chan.asyncOpen(listener, null); 1.268 +} 1.269 + 1.270 +function test_http2_push1() { 1.271 + var chan = makeChan("https://localhost:6944/push"); 1.272 + chan.loadGroup = loadGroup; 1.273 + var listener = new Http2PushListener(); 1.274 + chan.asyncOpen(listener, chan); 1.275 +} 1.276 + 1.277 +function test_http2_push2() { 1.278 + var chan = makeChan("https://localhost:6944/push.js"); 1.279 + chan.loadGroup = loadGroup; 1.280 + var listener = new Http2PushListener(); 1.281 + chan.asyncOpen(listener, chan); 1.282 +} 1.283 + 1.284 +function test_http2_push3() { 1.285 + var chan = makeChan("https://localhost:6944/push2"); 1.286 + chan.loadGroup = loadGroup; 1.287 + var listener = new Http2PushListener(); 1.288 + chan.asyncOpen(listener, chan); 1.289 +} 1.290 + 1.291 +function test_http2_push4() { 1.292 + var chan = makeChan("https://localhost:6944/push2.js"); 1.293 + chan.loadGroup = loadGroup; 1.294 + var listener = new Http2PushListener(); 1.295 + chan.asyncOpen(listener, chan); 1.296 +} 1.297 + 1.298 +// Make sure we handle GETs that cover more than 2 frames properly 1.299 +function test_http2_big() { 1.300 + var chan = makeChan("https://localhost:6944/big"); 1.301 + var listener = new Http2BigListener(); 1.302 + chan.asyncOpen(listener, null); 1.303 +} 1.304 + 1.305 +// Support for doing a POST 1.306 +function do_post(content, chan, listener) { 1.307 + var stream = Cc["@mozilla.org/io/string-input-stream;1"] 1.308 + .createInstance(Ci.nsIStringInputStream); 1.309 + stream.data = content; 1.310 + 1.311 + var uchan = chan.QueryInterface(Ci.nsIUploadChannel); 1.312 + uchan.setUploadStream(stream, "text/plain", stream.available()); 1.313 + 1.314 + chan.requestMethod = "POST"; 1.315 + 1.316 + chan.asyncOpen(listener, null); 1.317 +} 1.318 + 1.319 +// Make sure we can do a simple POST 1.320 +function test_http2_post() { 1.321 + var chan = makeChan("https://localhost:6944/post"); 1.322 + var listener = new Http2PostListener(md5s[0]); 1.323 + do_post(posts[0], chan, listener); 1.324 +} 1.325 + 1.326 +// Make sure we can do a POST that covers more than 2 frames 1.327 +function test_http2_post_big() { 1.328 + var chan = makeChan("https://localhost:6944/post"); 1.329 + var listener = new Http2PostListener(md5s[1]); 1.330 + do_post(posts[1], chan, listener); 1.331 +} 1.332 + 1.333 +// hack - the header test resets the multiplex object on the server, 1.334 +// so make sure header is always run before the multiplex test. 1.335 +// 1.336 +// make sure post_big runs first to test race condition in restarting 1.337 +// a stalled stream when a SETTINGS frame arrives 1.338 +var tests = [ test_http2_post_big 1.339 + , test_http2_basic 1.340 + , test_http2_push1 1.341 + , test_http2_push2 1.342 + , test_http2_push3 1.343 + , test_http2_push4 1.344 + , test_http2_xhr 1.345 + , test_http2_header 1.346 + , test_http2_cookie_crumbling 1.347 + , test_http2_multiplex 1.348 + , test_http2_big 1.349 + , test_http2_post 1.350 + ]; 1.351 +var current_test = 0; 1.352 + 1.353 +function run_next_test() { 1.354 + if (current_test < tests.length) { 1.355 + tests[current_test](); 1.356 + current_test++; 1.357 + do_test_pending(); 1.358 + } 1.359 +} 1.360 + 1.361 +// Support for making sure we can talk to the invalid cert the server presents 1.362 +var CertOverrideListener = function(host, port, bits) { 1.363 + this.host = host; 1.364 + if (port) { 1.365 + this.port = port; 1.366 + } 1.367 + this.bits = bits; 1.368 +}; 1.369 + 1.370 +CertOverrideListener.prototype = { 1.371 + host: null, 1.372 + port: -1, 1.373 + bits: null, 1.374 + 1.375 + getInterface: function(aIID) { 1.376 + return this.QueryInterface(aIID); 1.377 + }, 1.378 + 1.379 + QueryInterface: function(aIID) { 1.380 + if (aIID.equals(Ci.nsIBadCertListener2) || 1.381 + aIID.equals(Ci.nsIInterfaceRequestor) || 1.382 + aIID.equals(Ci.nsISupports)) 1.383 + return this; 1.384 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.385 + }, 1.386 + 1.387 + notifyCertProblem: function(socketInfo, sslStatus, targetHost) { 1.388 + var cert = sslStatus.QueryInterface(Ci.nsISSLStatus).serverCert; 1.389 + var cos = Cc["@mozilla.org/security/certoverride;1"]. 1.390 + getService(Ci.nsICertOverrideService); 1.391 + cos.rememberValidityOverride(this.host, this.port, cert, this.bits, false); 1.392 + return true; 1.393 + }, 1.394 +}; 1.395 + 1.396 +function addCertOverride(host, port, bits) { 1.397 + var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] 1.398 + .createInstance(Ci.nsIXMLHttpRequest); 1.399 + try { 1.400 + var url; 1.401 + if (port) { 1.402 + url = "https://" + host + ":" + port + "/"; 1.403 + } else { 1.404 + url = "https://" + host + "/"; 1.405 + } 1.406 + req.open("GET", url, false); 1.407 + req.channel.notificationCallbacks = new CertOverrideListener(host, port, bits); 1.408 + req.send(null); 1.409 + } catch (e) { 1.410 + // This will fail since the server is not trusted yet 1.411 + } 1.412 +} 1.413 + 1.414 +var prefs; 1.415 +var spdypref; 1.416 +var spdy3pref; 1.417 +var spdypush; 1.418 +var http2pref; 1.419 + 1.420 +var loadGroup; 1.421 + 1.422 +function resetPrefs() { 1.423 + prefs.setBoolPref("network.http.spdy.enabled", spdypref); 1.424 + prefs.setBoolPref("network.http.spdy.enabled.v3", spdy3pref); 1.425 + prefs.setBoolPref("network.http.spdy.allow-push", spdypush); 1.426 + prefs.setBoolPref("network.http.spdy.enabled.http2draft", http2pref); 1.427 +} 1.428 + 1.429 +function run_test() { 1.430 + // Set to allow the cert presented by our SPDY server 1.431 + do_get_profile(); 1.432 + var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); 1.433 + var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit"); 1.434 + prefs.setIntPref("network.http.speculative-parallel-limit", 0); 1.435 + 1.436 + addCertOverride("localhost", 6944, 1.437 + Ci.nsICertOverrideService.ERROR_UNTRUSTED | 1.438 + Ci.nsICertOverrideService.ERROR_MISMATCH | 1.439 + Ci.nsICertOverrideService.ERROR_TIME); 1.440 + 1.441 + prefs.setIntPref("network.http.speculative-parallel-limit", oldPref); 1.442 + 1.443 + // Enable all versions of spdy to see that we auto negotiate http/2 1.444 + spdypref = prefs.getBoolPref("network.http.spdy.enabled"); 1.445 + spdy3pref = prefs.getBoolPref("network.http.spdy.enabled.v3"); 1.446 + spdypush = prefs.getBoolPref("network.http.spdy.allow-push"); 1.447 + http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2draft"); 1.448 + prefs.setBoolPref("network.http.spdy.enabled", true); 1.449 + prefs.setBoolPref("network.http.spdy.enabled.v3", true); 1.450 + prefs.setBoolPref("network.http.spdy.allow-push", true); 1.451 + prefs.setBoolPref("network.http.spdy.enabled.http2draft", true); 1.452 + 1.453 + loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup); 1.454 + 1.455 + // And make go! 1.456 + run_next_test(); 1.457 +}