netwerk/test/unit/test_http2.js

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

     1 // test HTTP/2
     3 var Ci = Components.interfaces;
     4 var Cc = Components.classes;
     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 }
    15 var posts = [];
    16 posts.push(generateContent(10));
    17 posts.push(generateContent(250000));
    19 // pre-calculated md5sums (in hex) of the above posts
    20 var md5s = ['f1b708bba17f1ce948dc979f4d7092bc',
    21             '2ef8d3b6c8f329318eb1a119b12622b6'];
    23 var bigListenerData = generateContent(128 * 1024);
    24 var bigListenerMD5 = '8f607cfdd2c87d6a7eedb657dafbd836';
    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 }
    40 var Http2CheckListener = function() {};
    42 Http2CheckListener.prototype = {
    43   onStartRequestFired: false,
    44   onDataAvailableFired: false,
    45   isHttp2Connection: false,
    47   onStartRequest: function testOnStartRequest(request, ctx) {
    48     this.onStartRequestFired = true;
    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");
    55     do_check_eq(request.responseStatus, 200);
    56     do_check_eq(request.requestSucceeded, true);
    57   },
    59   onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
    60     this.onDataAvailableFired = true;
    61     this.isHttp2Connection = checkIsHttp2(request);
    63     read_stream(stream, cnt);
    64   },
    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);
    71     run_next_test();
    72     do_test_finished();
    73   }
    74 };
    76 /*
    77  * Support for testing valid multiplexing of streams
    78  */
    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 }
    91 /* Listener class to control the testing of multiplexing */
    92 var Http2MultiplexListener = function() {};
    94 Http2MultiplexListener.prototype = new Http2CheckListener();
    96 Http2MultiplexListener.prototype.streamID = 0;
    97 Http2MultiplexListener.prototype.buffer = "";
    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 };
   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);
   113   // This is what does most of the hard work for us
   114   register_completed_channel(this);
   115 };
   117 // Does the appropriate checks for header gatewaying
   118 var Http2HeaderListener = function(name, callback) {
   119   this.name = name;
   120   this.callback = callback;
   121 };
   123 Http2HeaderListener.prototype = new Http2CheckListener();
   124 Http2HeaderListener.prototype.value = "";
   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 };
   135 var Http2PushListener = function() {};
   137 Http2PushListener.prototype = new Http2CheckListener();
   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 };
   149 // Does the appropriate checks for a large GET response
   150 var Http2BigListener = function() {};
   152 Http2BigListener.prototype = new Http2CheckListener();
   153 Http2BigListener.prototype.buffer = "";
   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 };
   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);
   169   // Don't want to flood output, so don't use do_check_eq
   170   do_check_true(this.buffer == bigListenerData);
   172   run_next_test();
   173   do_test_finished();
   174 };
   176 // Does the appropriate checks for POSTs
   177 var Http2PostListener = function(expected_md5) {
   178   this.expected_md5 = expected_md5;
   179 };
   181 Http2PostListener.prototype = new Http2CheckListener();
   182 Http2PostListener.prototype.expected_md5 = "";
   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 };
   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);
   195   return chan;
   196 }
   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 }
   205 // Support for making sure XHR works over SPDY
   206 function checkXhr(xhr) {
   207   if (xhr.readyState != 4) {
   208     return;
   209   }
   211   do_check_eq(xhr.status, 200);
   212   do_check_eq(checkIsHttp2(xhr), true);
   213   run_next_test();
   214   do_test_finished();
   215 }
   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 }
   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 }
   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 }
   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 }
   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 }
   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 }
   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 }
   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 }
   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 }
   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;
   308   var uchan = chan.QueryInterface(Ci.nsIUploadChannel);
   309   uchan.setUploadStream(stream, "text/plain", stream.available());
   311   chan.requestMethod = "POST";
   313   chan.asyncOpen(listener, null);
   314 }
   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 }
   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 }
   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;
   350 function run_next_test() {
   351   if (current_test < tests.length) {
   352     tests[current_test]();
   353     current_test++;
   354     do_test_pending();
   355   }
   356 }
   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 };
   367 CertOverrideListener.prototype = {
   368   host: null,
   369   port: -1,
   370   bits: null,
   372   getInterface: function(aIID) {
   373     return this.QueryInterface(aIID);
   374   },
   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   },
   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 };
   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 }
   411 var prefs;
   412 var spdypref;
   413 var spdy3pref;
   414 var spdypush;
   415 var http2pref;
   417 var loadGroup;
   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 }
   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);
   433   addCertOverride("localhost", 6944,
   434                   Ci.nsICertOverrideService.ERROR_UNTRUSTED |
   435                   Ci.nsICertOverrideService.ERROR_MISMATCH |
   436                   Ci.nsICertOverrideService.ERROR_TIME);
   438   prefs.setIntPref("network.http.speculative-parallel-limit", oldPref);
   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);
   450   loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup);
   452   // And make go!
   453   run_next_test();
   454 }

mercurial