Bug 1857386 [wpt PR 42383] - Update wpt metadata, a=testonly
[gecko.git] / netwerk / test / unit / test_http3.js
blob7f3f5b118d0ebc4a27ed1c887774575ccd1b6235
1 "use strict";
3 const { HttpServer } = ChromeUtils.importESModule(
4   "resource://testing-common/httpd.sys.mjs"
5 );
7 // Generate a post with known pre-calculated md5 sum.
8 function generateContent(size) {
9   let content = "";
10   for (let i = 0; i < size; i++) {
11     content += "0";
12   }
13   return content;
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;
21 let h1Server = null;
22 let h3Route;
23 let httpsOrigin;
24 let httpOrigin;
25 let h3AltSvc;
27 let prefs;
29 let tests = [
30   // This test must be the first because it setsup alt-svc connection, that
31   // other tests use.
32   test_https_alt_svc,
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,
38   test_post,
39   test_patch,
40   test_http_alt_svc,
41   test_slow_receiver,
42   // This test should be at the end, because it will close http3
43   // connection and the transaction will switch to already existing http2
44   // connection.
45   // TODO: Bug 1582667 should try to fix issue with connection being closed.
46   test_version_fallback,
47   testsDone,
50 let current_test = 0;
52 function run_next_test() {
53   if (current_test < tests.length) {
54     dump("starting test number " + current_test + "\n");
55     tests[current_test]();
56     current_test++;
57   }
60 function run_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;
70   do_get_profile();
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
76   // following pref:
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
82   // signing cert.
83   let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
84     Ci.nsIX509CertDB
85   );
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);
93   h1Server.start(-1);
94   h1Server.identity.setPrimary(
95     "http",
96     "foo.example.com",
97     h1Server.identity.primaryPort
98   );
99   httpOrigin = "http://foo.example.com:" + h1Server.identity.primaryPort + "/";
101   run_next_test();
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);
113   try {
114     let hval = "h3-29=" + metadata.getHeader("x-altsvc");
115     response.setHeader("Alt-Svc", hval, false);
116   } catch (e) {}
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({
137     uri,
138     loadUsingSystemPrincipal: true,
139   }).QueryInterface(Ci.nsIHttpChannel);
140   chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
141   return chan;
144 let Http3CheckListener = function () {};
146 Http3CheckListener.prototype = {
147   onDataAvailableFired: false,
148   expectedStatus: Cr.NS_OK,
149   expectedRoute: "",
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);
157     }
158   },
160   onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
161     this.onDataAvailableFired = true;
162     read_stream(stream, cnt);
163   },
165   onStopRequest: function testOnStopRequest(request, status) {
166     Assert.equal(status, this.expectedStatus);
167     let routed = "NA";
168     try {
169       routed = request.getRequestHeader("Alt-Used");
170     } catch (e) {}
171     dump("routed is " + routed + "\n");
173     Assert.equal(routed, this.expectedRoute);
175     if (Components.isSuccessCode(this.expectedStatus)) {
176       let httpVersion = "";
177       try {
178         httpVersion = request.protocolVersion;
179       } catch (e) {}
180       Assert.equal(httpVersion, "h3-29");
181       Assert.equal(this.onDataAvailableFired, true);
182       Assert.equal(request.getResponseHeader("X-Firefox-Http3"), "h3-29");
183     }
184     run_next_test();
185     do_test_finished();
186   },
189 let WaitForHttp3Listener = function () {};
191 WaitForHttp3Listener.prototype = new Http3CheckListener();
193 WaitForHttp3Listener.prototype.uri = "";
194 WaitForHttp3Listener.prototype.h3AltSvc = "";
196 WaitForHttp3Listener.prototype.onStopRequest = function testOnStopRequest(
197   request,
198   status
199 ) {
200   Assert.equal(status, this.expectedStatus);
202   let routed = "NA";
203   try {
204     routed = request.getRequestHeader("Alt-Used");
205   } catch (e) {}
206   dump("routed is " + routed + "\n");
208   let httpVersion = "";
209   try {
210     httpVersion = request.protocolVersion;
211   } catch (e) {}
213   if (routed == this.expectedRoute) {
214     Assert.equal(routed, this.expectedRoute); // always true, but a useful log
215     Assert.equal(httpVersion, "h3-29");
216     run_next_test();
217   } else {
218     dump("poll later for alt svc mapping\n");
219     if (httpVersion == "h2") {
220       request.QueryInterface(Ci.nsIHttpChannelInternal);
221       Assert.ok(request.supportsHTTP3);
222     }
223     do_test_pending();
224     do_timeout(500, () => {
225       doTest(this.uri, this.expectedRoute, this.h3AltSvc);
226     });
227   }
229   do_test_finished();
232 function doTest(uri, expectedRoute, altSvc) {
233   let chan = makeChan(uri);
234   let listener = new WaitForHttp3Listener();
235   listener.uri = uri;
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");
246   do_test_pending();
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 = "";
266     try {
267       need_cancel = request.getRequestHeader("CancelMe");
268     } catch (e) {}
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.");
276     } else {
277       Assert.equal(request.status, this.with_error);
278       this.error_found_onstart = true;
279     }
280   },
282   onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
283     read_stream(stream, cnt);
284   },
286   onStopRequest: function testOnStopRequest(request, status) {
287     let routed = "";
288     try {
289       routed = request.getRequestHeader("Alt-Used");
290     } catch (e) {}
291     Assert.equal(routed, this.expectedRoute);
293     if (Components.isSuccessCode(request.status)) {
294       let httpVersion = "";
295       try {
296         httpVersion = request.protocolVersion;
297       } catch (e) {}
298       Assert.equal(httpVersion, "h3-29");
299     }
301     if (!Components.isSuccessCode(request.status)) {
302       if (this.error_found_onstop) {
303         do_throw("We should have only one request failing.");
304       } else {
305         Assert.equal(request.status, this.with_error);
306         this.error_found_onstop = true;
307       }
308     }
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);
314       } else {
315         Assert.ok(this.error_found_onstart || this.need_cancel_found);
316         Assert.equal(this.error_found_onstop, true);
317       }
318       run_next_test();
319     }
320     do_test_finished();
321   },
324 // Multiple requests
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);
335     do_test_pending();
336   }
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);
348   do_test_pending();
351 let CancelRequestListener = function () {};
353 CancelRequestListener.prototype = new Http3CheckListener();
355 CancelRequestListener.prototype.expectedStatus = Cr.NS_ERROR_ABORT;
357 CancelRequestListener.prototype.onStartRequest = function testOnStartRequest(
358   request
359 ) {
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);
374   do_test_pending();
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";
388     if (i == 4) {
389       // Add a request that will be cancelled by the server.
390       uri = httpsOrigin + "RequestCancelled";
391     }
392     let chan = makeChan(uri);
393     chan.asyncOpen(listener);
394     do_test_pending();
395   }
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");
408     if (i == 4) {
409       // MultipleListener will cancel request with this header.
410       chan.setRequestHeader("CancelMe", "true", false);
411     }
412     chan.asyncOpen(listener);
413     do_test_pending();
414   }
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
430   );
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() {
443   dump("test_post()");
444   let chan = makeChan(httpsOrigin + "post");
445   let listener = new PostListener();
446   listener.expectedRoute = h3Route;
447   do_post(post, chan, listener, "POST");
448   do_test_pending();
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");
458   do_test_pending();
461 // Test alt-svc for http (without s)
462 function test_http_alt_svc() {
463   dump("test_http_alt_svc()\n");
465   do_test_pending();
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 (
475   request,
476   stream,
477   off,
478   cnt
479 ) {
480   this.onDataAvailableFired = true;
481   this.count += cnt;
482   read_stream(stream, cnt);
485 SlowReceiverListener.prototype.onStopRequest = function (request, status) {
486   Assert.equal(status, this.expectedStatus);
487   Assert.equal(this.count, 10000000);
488   let routed = "NA";
489   try {
490     routed = request.getRequestHeader("Alt-Used");
491   } catch (e) {}
492   dump("routed is " + routed + "\n");
494   Assert.equal(routed, this.expectedRoute);
496   if (Components.isSuccessCode(this.expectedStatus)) {
497     let httpVersion = "";
498     try {
499       httpVersion = request.protocolVersion;
500     } catch (e) {}
501     Assert.equal(httpVersion, "h3-29");
502     Assert.equal(this.onDataAvailableFired, true);
503   }
504   run_next_test();
505   do_test_finished();
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);
514   do_test_pending();
515   chan.suspend();
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);
527   },
529   onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
530     read_stream(stream, cnt);
531   },
533   onStopRequest: function testOnStopRequest(request, status) {
534     Assert.equal(status, Cr.NS_OK);
535     let routed = "NA";
536     try {
537       routed = request.getRequestHeader("Alt-Used");
538     } catch (e) {}
539     dump("routed is " + routed + "\n");
541     Assert.equal(routed, "0");
543     let httpVersion = "";
544     try {
545       httpVersion = request.protocolVersion;
546     } catch (e) {}
547     Assert.equal(httpVersion, "http/1.1");
548     run_next_test();
549     do_test_finished();
550   },
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);
560   do_test_pending();
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");
568   dump("testDone\n");
569   do_test_pending();
570   h1Server.stop(do_test_finished);