netwerk/test/unit/test_http2.js

Wed, 31 Dec 2014 06:55:46 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:46 +0100
changeset 1
ca08bd8f51b2
permissions
-rw-r--r--

Added tag TORBROWSER_REPLICA for changeset 6474c204b198

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

mercurial