3 const { HttpServer } = ChromeUtils.importESModule(
4 "resource://testing-common/httpd.sys.mjs"
7 // Generate a post with known pre-calculated md5 sum.
8 function generateContent(size) {
10 for (let i = 0; i < size; i++) {
16 let post = generateContent(10);
18 // Max concurent stream number in neqo is 100.
19 // Openning 120 streams will test queuing of streams.
20 let number_of_parallel_requests = 120;
30 // This test must be the first because it setsup alt-svc connection, that
33 test_multiple_requests,
34 test_request_cancelled_by_server,
35 test_stream_cancelled_by_necko,
36 test_multiple_request_one_is_cancelled,
37 test_multiple_request_one_is_cancelled_by_necko,
42 // This test should be at the end, because it will close http3
43 // connection and the transaction will switch to already existing http2
45 // TODO: Bug 1582667 should try to fix issue with connection being closed.
46 test_version_fallback,
52 function run_next_test() {
53 if (current_test < tests.length) {
54 dump("starting test number " + current_test + "\n");
55 tests[current_test]();
61 let h2Port = Services.env.get("MOZHTTP2_PORT");
62 Assert.notEqual(h2Port, null);
63 Assert.notEqual(h2Port, "");
64 let h3Port = Services.env.get("MOZHTTP3_PORT");
65 Assert.notEqual(h3Port, null);
66 Assert.notEqual(h3Port, "");
67 h3AltSvc = ":" + h3Port;
69 h3Route = "foo.example.com:" + h3Port;
71 prefs = Services.prefs;
73 prefs.setBoolPref("network.http.http3.enable", true);
74 prefs.setCharPref("network.dns.localDomains", "foo.example.com");
75 // We always resolve elements of localDomains as it's hardcoded without the
77 prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
78 prefs.setBoolPref("network.http.altsvc.oe", true);
80 // The certificate for the http3server server is for foo.example.com and
81 // is signed by http2-ca.pem so add that cert to the trust list as a
83 let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
86 addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
87 httpsOrigin = "https://foo.example.com:" + h2Port + "/";
89 h1Server = new HttpServer();
90 h1Server.registerPathHandler("/http3-test", h1Response);
91 h1Server.registerPathHandler("/.well-known/http-opportunistic", h1ServerWK);
92 h1Server.registerPathHandler("/VersionFallback", h1Response);
94 h1Server.identity.setPrimary(
97 h1Server.identity.primaryPort
99 httpOrigin = "http://foo.example.com:" + h1Server.identity.primaryPort + "/";
104 function h1Response(metadata, response) {
105 response.setStatusLine(metadata.httpVersion, 200, "OK");
106 response.setHeader("Content-Type", "text/plain", false);
107 response.setHeader("Connection", "close", false);
108 response.setHeader("Cache-Control", "no-cache", false);
109 response.setHeader("Access-Control-Allow-Origin", "*", false);
110 response.setHeader("Access-Control-Allow-Method", "GET", false);
111 response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false);
114 let hval = "h3-29=" + metadata.getHeader("x-altsvc");
115 response.setHeader("Alt-Svc", hval, false);
118 let body = "Q: What did 0 say to 8? A: Nice Belt!\n";
119 response.bodyOutputStream.write(body, body.length);
122 function h1ServerWK(metadata, response) {
123 response.setStatusLine(metadata.httpVersion, 200, "OK");
124 response.setHeader("Content-Type", "application/json", false);
125 response.setHeader("Connection", "close", false);
126 response.setHeader("Cache-Control", "no-cache", false);
127 response.setHeader("Access-Control-Allow-Origin", "*", false);
128 response.setHeader("Access-Control-Allow-Method", "GET", false);
129 response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false);
131 let body = '["http://foo.example.com:' + h1Server.identity.primaryPort + '"]';
132 response.bodyOutputStream.write(body, body.length);
135 function makeChan(uri) {
136 let chan = NetUtil.newChannel({
138 loadUsingSystemPrincipal: true,
139 }).QueryInterface(Ci.nsIHttpChannel);
140 chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
144 let Http3CheckListener = function () {};
146 Http3CheckListener.prototype = {
147 onDataAvailableFired: false,
148 expectedStatus: Cr.NS_OK,
151 onStartRequest: function testOnStartRequest(request) {
152 Assert.ok(request instanceof Ci.nsIHttpChannel);
154 Assert.equal(request.status, this.expectedStatus);
155 if (Components.isSuccessCode(this.expectedStatus)) {
156 Assert.equal(request.responseStatus, 200);
160 onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
161 this.onDataAvailableFired = true;
162 read_stream(stream, cnt);
165 onStopRequest: function testOnStopRequest(request, status) {
166 Assert.equal(status, this.expectedStatus);
169 routed = request.getRequestHeader("Alt-Used");
171 dump("routed is " + routed + "\n");
173 Assert.equal(routed, this.expectedRoute);
175 if (Components.isSuccessCode(this.expectedStatus)) {
176 let httpVersion = "";
178 httpVersion = request.protocolVersion;
180 Assert.equal(httpVersion, "h3-29");
181 Assert.equal(this.onDataAvailableFired, true);
182 Assert.equal(request.getResponseHeader("X-Firefox-Http3"), "h3-29");
189 let WaitForHttp3Listener = function () {};
191 WaitForHttp3Listener.prototype = new Http3CheckListener();
193 WaitForHttp3Listener.prototype.uri = "";
194 WaitForHttp3Listener.prototype.h3AltSvc = "";
196 WaitForHttp3Listener.prototype.onStopRequest = function testOnStopRequest(
200 Assert.equal(status, this.expectedStatus);
204 routed = request.getRequestHeader("Alt-Used");
206 dump("routed is " + routed + "\n");
208 let httpVersion = "";
210 httpVersion = request.protocolVersion;
213 if (routed == this.expectedRoute) {
214 Assert.equal(routed, this.expectedRoute); // always true, but a useful log
215 Assert.equal(httpVersion, "h3-29");
218 dump("poll later for alt svc mapping\n");
219 if (httpVersion == "h2") {
220 request.QueryInterface(Ci.nsIHttpChannelInternal);
221 Assert.ok(request.supportsHTTP3);
224 do_timeout(500, () => {
225 doTest(this.uri, this.expectedRoute, this.h3AltSvc);
232 function doTest(uri, expectedRoute, altSvc) {
233 let chan = makeChan(uri);
234 let listener = new WaitForHttp3Listener();
236 listener.expectedRoute = expectedRoute;
237 listener.h3AltSvc = altSvc;
238 chan.setRequestHeader("x-altsvc", altSvc, false);
239 chan.asyncOpen(listener);
242 // Test Alt-Svc for http3.
243 // H2 server returns alt-svc=h3-29=:h3port
244 function test_https_alt_svc() {
245 dump("test_https_alt_svc()\n");
247 doTest(httpsOrigin + "http3-test", h3Route, h3AltSvc);
250 // Listener for a number of parallel requests. if with_error is set, one of
251 // the channels will be cancelled (by the server or in onStartRequest).
252 let MultipleListener = function () {};
254 MultipleListener.prototype = {
255 number_of_parallel_requests: 0,
256 with_error: Cr.NS_OK,
257 count_of_done_requests: 0,
258 error_found_onstart: false,
259 error_found_onstop: false,
260 need_cancel_found: false,
262 onStartRequest: function testOnStartRequest(request) {
263 Assert.ok(request instanceof Ci.nsIHttpChannel);
265 let need_cancel = "";
267 need_cancel = request.getRequestHeader("CancelMe");
269 if (need_cancel != "") {
270 this.need_cancel_found = true;
271 request.cancel(Cr.NS_ERROR_ABORT);
272 } else if (Components.isSuccessCode(request.status)) {
273 Assert.equal(request.responseStatus, 200);
274 } else if (this.error_found_onstart) {
275 do_throw("We should have only one request failing.");
277 Assert.equal(request.status, this.with_error);
278 this.error_found_onstart = true;
282 onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
283 read_stream(stream, cnt);
286 onStopRequest: function testOnStopRequest(request) {
289 routed = request.getRequestHeader("Alt-Used");
291 Assert.equal(routed, this.expectedRoute);
293 if (Components.isSuccessCode(request.status)) {
294 let httpVersion = "";
296 httpVersion = request.protocolVersion;
298 Assert.equal(httpVersion, "h3-29");
301 if (!Components.isSuccessCode(request.status)) {
302 if (this.error_found_onstop) {
303 do_throw("We should have only one request failing.");
305 Assert.equal(request.status, this.with_error);
306 this.error_found_onstop = true;
309 this.count_of_done_requests++;
310 if (this.count_of_done_requests == this.number_of_parallel_requests) {
311 if (Components.isSuccessCode(this.with_error)) {
312 Assert.equal(this.error_found_onstart, false);
313 Assert.equal(this.error_found_onstop, false);
315 Assert.ok(this.error_found_onstart || this.need_cancel_found);
316 Assert.equal(this.error_found_onstop, true);
325 function test_multiple_requests() {
326 dump("test_multiple_requests()\n");
328 let listener = new MultipleListener();
329 listener.number_of_parallel_requests = number_of_parallel_requests;
330 listener.expectedRoute = h3Route;
332 for (let i = 0; i < number_of_parallel_requests; i++) {
333 let chan = makeChan(httpsOrigin + "20000");
334 chan.asyncOpen(listener);
339 // A request cancelled by a server.
340 function test_request_cancelled_by_server() {
341 dump("test_request_cancelled_by_server()\n");
343 let listener = new Http3CheckListener();
344 listener.expectedStatus = Cr.NS_ERROR_NET_INTERRUPT;
345 listener.expectedRoute = h3Route;
346 let chan = makeChan(httpsOrigin + "RequestCancelled");
347 chan.asyncOpen(listener);
351 let CancelRequestListener = function () {};
353 CancelRequestListener.prototype = new Http3CheckListener();
355 CancelRequestListener.prototype.expectedStatus = Cr.NS_ERROR_ABORT;
357 CancelRequestListener.prototype.onStartRequest = function testOnStartRequest(
360 Assert.ok(request instanceof Ci.nsIHttpChannel);
362 Assert.equal(Components.isSuccessCode(request.status), true);
363 request.cancel(Cr.NS_ERROR_ABORT);
366 // Cancel stream after OnStartRequest.
367 function test_stream_cancelled_by_necko() {
368 dump("test_stream_cancelled_by_necko()\n");
370 let listener = new CancelRequestListener();
371 listener.expectedRoute = h3Route;
372 let chan = makeChan(httpsOrigin + "20000");
373 chan.asyncOpen(listener);
377 // Multiple requests, one gets cancelled by the server, the other should finish normally.
378 function test_multiple_request_one_is_cancelled() {
379 dump("test_multiple_request_one_is_cancelled()\n");
381 let listener = new MultipleListener();
382 listener.number_of_parallel_requests = number_of_parallel_requests;
383 listener.with_error = Cr.NS_ERROR_NET_INTERRUPT;
384 listener.expectedRoute = h3Route;
386 for (let i = 0; i < number_of_parallel_requests; i++) {
387 let uri = httpsOrigin + "20000";
389 // Add a request that will be cancelled by the server.
390 uri = httpsOrigin + "RequestCancelled";
392 let chan = makeChan(uri);
393 chan.asyncOpen(listener);
398 // Multiple requests, one gets cancelled by us, the other should finish normally.
399 function test_multiple_request_one_is_cancelled_by_necko() {
400 dump("test_multiple_request_one_is_cancelled_by_necko()\n");
402 let listener = new MultipleListener();
403 listener.number_of_parallel_requests = number_of_parallel_requests;
404 listener.with_error = Cr.NS_ERROR_ABORT;
405 listener.expectedRoute = h3Route;
406 for (let i = 0; i < number_of_parallel_requests; i++) {
407 let chan = makeChan(httpsOrigin + "20000");
409 // MultipleListener will cancel request with this header.
410 chan.setRequestHeader("CancelMe", "true", false);
412 chan.asyncOpen(listener);
417 let PostListener = function () {};
419 PostListener.prototype = new Http3CheckListener();
421 PostListener.prototype.onDataAvailable = function (request, stream, off, cnt) {
422 this.onDataAvailableFired = true;
423 read_stream(stream, cnt);
426 // Support for doing a POST
427 function do_post(content, chan, listener, method) {
428 let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
429 Ci.nsIStringInputStream
431 stream.data = content;
433 let uchan = chan.QueryInterface(Ci.nsIUploadChannel);
434 uchan.setUploadStream(stream, "text/plain", stream.available());
436 chan.requestMethod = method;
438 chan.asyncOpen(listener);
441 // Test a simple POST
442 function test_post() {
444 let chan = makeChan(httpsOrigin + "post");
445 let listener = new PostListener();
446 listener.expectedRoute = h3Route;
447 do_post(post, chan, listener, "POST");
451 // Test a simple PATCH
452 function test_patch() {
453 dump("test_patch()");
454 let chan = makeChan(httpsOrigin + "patch");
455 let listener = new PostListener();
456 listener.expectedRoute = h3Route;
457 do_post(post, chan, listener, "PATCH");
461 // Test alt-svc for http (without s)
462 function test_http_alt_svc() {
463 dump("test_http_alt_svc()\n");
466 doTest(httpOrigin + "http3-test", h3Route, h3AltSvc);
469 let SlowReceiverListener = function () {};
471 SlowReceiverListener.prototype = new Http3CheckListener();
472 SlowReceiverListener.prototype.count = 0;
474 SlowReceiverListener.prototype.onDataAvailable = function (
480 this.onDataAvailableFired = true;
482 read_stream(stream, cnt);
485 SlowReceiverListener.prototype.onStopRequest = function (request, status) {
486 Assert.equal(status, this.expectedStatus);
487 Assert.equal(this.count, 10000000);
490 routed = request.getRequestHeader("Alt-Used");
492 dump("routed is " + routed + "\n");
494 Assert.equal(routed, this.expectedRoute);
496 if (Components.isSuccessCode(this.expectedStatus)) {
497 let httpVersion = "";
499 httpVersion = request.protocolVersion;
501 Assert.equal(httpVersion, "h3-29");
502 Assert.equal(this.onDataAvailableFired, true);
508 function test_slow_receiver() {
509 dump("test_slow_receiver()\n");
510 let chan = makeChan(httpsOrigin + "10000000");
511 let listener = new SlowReceiverListener();
512 listener.expectedRoute = h3Route;
513 chan.asyncOpen(listener);
516 do_timeout(1000, chan.resume);
519 let CheckFallbackListener = function () {};
521 CheckFallbackListener.prototype = {
522 onStartRequest: function testOnStartRequest(request) {
523 Assert.ok(request instanceof Ci.nsIHttpChannel);
525 Assert.equal(request.status, Cr.NS_OK);
526 Assert.equal(request.responseStatus, 200);
529 onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
530 read_stream(stream, cnt);
533 onStopRequest: function testOnStopRequest(request, status) {
534 Assert.equal(status, Cr.NS_OK);
537 routed = request.getRequestHeader("Alt-Used");
539 dump("routed is " + routed + "\n");
541 Assert.equal(routed, "0");
543 let httpVersion = "";
545 httpVersion = request.protocolVersion;
547 Assert.equal(httpVersion, "http/1.1");
553 // Server cancels request with VersionFallback.
554 function test_version_fallback() {
555 dump("test_version_fallback()\n");
557 let chan = makeChan(httpsOrigin + "VersionFallback");
558 let listener = new CheckFallbackListener();
559 chan.asyncOpen(listener);
563 function testsDone() {
564 prefs.clearUserPref("network.http.http3.enable");
565 prefs.clearUserPref("network.dns.localDomains");
566 prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
567 prefs.clearUserPref("network.http.altsvc.oe");
570 h1Server.stop(do_test_finished);