commit 73e5637f996a553258ee097bad97a2cc3498cc4b
parent 13b332cedd5cb51ced0cf85c03681da7dc35ede3
Author: Kershaw Chang <kershaw@mozilla.com>
Date: Fri, 17 Oct 2025 21:42:21 +0000
Bug 1994648 - Run current HTTP/2 tests with HTTP/3 proxy, r=necko-reviewers,valentin
Differential Revision: https://phabricator.services.mozilla.com/D268838
Diffstat:
10 files changed, 555 insertions(+), 84 deletions(-)
diff --git a/netwerk/protocol/http/Http2Stream.cpp b/netwerk/protocol/http/Http2Stream.cpp
@@ -33,6 +33,12 @@ Http2Stream::Http2Stream(nsAHttpTransaction* httpTransaction,
Http2Stream::~Http2Stream() {}
void Http2Stream::CloseStream(nsresult reason) {
+ if (reason == NS_ERROR_NET_RESET) {
+ // If we got NS_ERROR_NET_RESET, the transaction will be retried. Keep the
+ // Alt-Svc in the connection info. Dropping it could trigger an
+ // unintended proxy connection fallback.
+ mTransaction->DoNotRemoveAltSvc();
+ }
mTransaction->Close(reason);
mSession = nullptr;
mClosed = true;
diff --git a/netwerk/protocol/http/Http3Session.cpp b/netwerk/protocol/http/Http3Session.cpp
@@ -2163,9 +2163,7 @@ void Http3Session::CloseStreamInternal(Http3StreamBase* aStream,
// Close(NS_OK) implies that the NeqoHttp3Conn will be closed, so we can only
// do this when there is no Http3Steeam, WebTransportSession and
// WebTransportStream.
- if ((mShouldClose || mGoawayReceived) &&
- (!mStreamTransactionHash.Count() && mWebTransportSessions.IsEmpty() &&
- mWebTransportStreams.IsEmpty() && mTunnelStreams.IsEmpty())) {
+ if ((mShouldClose || mGoawayReceived) && HasNoActiveStreams()) {
MOZ_ASSERT(!IsClosing());
Close(NS_OK);
}
@@ -2240,7 +2238,7 @@ void Http3Session::DontReuse() {
}
mShouldClose = true;
- if (!mStreamTransactionHash.Count()) {
+ if (HasNoActiveStreams()) {
// This is a temporary workaround and should be fixed properly in Happy
// Eyeballs project. We should not exclude this domain if
// Http3Session::DontReuse is called from
diff --git a/netwerk/protocol/http/Http3Session.h b/netwerk/protocol/http/Http3Session.h
@@ -470,6 +470,12 @@ class Http3Session final : public Http3SessionBase,
// are exchanged.
void FinishNegotiation(ExtendedConnectKind aKind, bool aSuccess);
+ inline bool HasNoActiveStreams() const {
+ return mStreamTransactionHash.Count() == 0 &&
+ mWebTransportSessions.IsEmpty() && mWebTransportStreams.IsEmpty() &&
+ mTunnelStreams.IsEmpty();
+ }
+
nsTArray<RefPtr<Http3StreamBase>> mWebTransportSessions;
nsTArray<RefPtr<Http3StreamBase>> mWebTransportStreams;
nsTArray<RefPtr<Http3StreamBase>> mTunnelStreams;
diff --git a/netwerk/test/httpserver/NodeServer.sys.mjs b/netwerk/test/httpserver/NodeServer.sys.mjs
@@ -1108,7 +1108,7 @@ export class HTTP3Server {
/// Stops the server
async stop() {
if (this.processId) {
- await NodeServer.kill(this.processId);
+ await NodeServer.kill(this.processId, true);
this.processId = undefined;
}
}
@@ -1171,13 +1171,13 @@ export class NodeServer {
return this.sendCommand(command, `/execute/${id}`);
}
// Shuts down the server
- static kill(id) {
- return this.sendCommand("", `/kill/${id}`);
+ static kill(id, ignoreError = false) {
+ return this.sendCommand("", `/kill/${id}`, ignoreError);
}
// Issues a request to the node server (handler defined in moz-http2.js)
// This method should not be called directly.
- static sendCommand(command, path) {
+ static sendCommand(command, path, ignoreError = false) {
let h2Port = Services.env.get("MOZNODE_EXEC_PORT");
if (!h2Port) {
throw new Error("Could not find MOZNODE_EXEC_PORT");
@@ -1224,6 +1224,10 @@ export class NodeServer {
if (x.error) {
let e = new Error(x.error, "", 0);
e.stack = x.errorStack;
+ if (ignoreError) {
+ resolve(undefined);
+ return;
+ }
reject(e);
return;
}
diff --git a/netwerk/test/unit/http2_test_common.js b/netwerk/test/unit/http2_test_common.js
@@ -64,6 +64,14 @@ Http2CheckListener.prototype = {
}
Assert.ok(request instanceof Ci.nsIHttpChannel);
+ if (this.noResponseStatus) {
+ Assert.throws(
+ () => request.responseStatus,
+ /NS_ERROR_NOT_AVAILABLE/,
+ "getting response status should throw"
+ );
+ return;
+ }
Assert.equal(request.requestSucceeded, this.shouldSucceed);
if (this.shouldSucceed) {
Assert.equal(request.responseStatus, 200);
@@ -314,8 +322,8 @@ async function test_http2_blocking_download(serverPort) {
}
// Make sure we make a HTTP2 connection and both us and the server mark it as such
-async function test_http2_basic(serverPort) {
- var chan = makeHTTPChannel(`https://localhost:${serverPort}/`);
+async function test_http2_basic(serverPort, origin = "localhost") {
+ var chan = makeHTTPChannel(`https://${origin}:${serverPort}/`);
var p = new Promise(resolve => {
var listener = new Http2CheckListener();
listener.finish = resolve;
@@ -324,9 +332,12 @@ async function test_http2_basic(serverPort) {
return p;
}
-async function test_http2_basic_unblocked_dep(serverPort) {
+async function test_http2_basic_unblocked_dep(
+ serverPort,
+ origin = "localhost"
+) {
var chan = makeHTTPChannel(
- `https://localhost:${serverPort}/basic_unblocked_dep`
+ `https://${origin}:${serverPort}/basic_unblocked_dep`
);
var cos = chan.QueryInterface(Ci.nsIClassOfService);
cos.addClassFlags(Ci.nsIClassOfService.Unblocked);
@@ -338,8 +349,8 @@ async function test_http2_basic_unblocked_dep(serverPort) {
}
// make sure we don't use h2 when disallowed
-async function test_http2_nospdy(serverPort) {
- var chan = makeHTTPChannel(`https://localhost:${serverPort}/`);
+async function test_http2_nospdy(serverPort, origin = "localhost") {
+ var chan = makeHTTPChannel(`https://${origin}:${serverPort}/`);
return new Promise(resolve => {
var listener = new Http2CheckListener();
listener.finish = resolve;
@@ -362,10 +373,10 @@ function checkXhr(xhr, finish) {
}
// Fires off an XHR request over h2
-async function test_http2_xhr(serverPort) {
+async function test_http2_xhr(serverPort, origin = "localhost") {
return new Promise(resolve => {
var req = new XMLHttpRequest();
- req.open("GET", `https://localhost:${serverPort}/`, true);
+ req.open("GET", `https://${origin}:${serverPort}/`, true);
req.addEventListener("readystatechange", function () {
checkXhr(req, resolve);
});
@@ -401,7 +412,11 @@ Http2ConcurrentListener.prototype.onStopRequest = function (request) {
}
};
-async function test_http2_concurrent(concurrent_channels, serverPort) {
+async function test_http2_concurrent(
+ concurrent_channels,
+ serverPort,
+ origin = "localhost"
+) {
var p = new Promise(resolve => {
var concurrent_listener = new Http2ConcurrentListener();
concurrent_listener.finish = resolve;
@@ -413,7 +428,7 @@ async function test_http2_concurrent(concurrent_channels, serverPort) {
for (var i = 0; i < concurrent_listener.target; i++) {
concurrent_channels[i] = makeHTTPChannel(
- `https://localhost:${serverPort}/750ms`
+ `https://${origin}:${serverPort}/750ms`
);
concurrent_channels[i].loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
concurrent_channels[i].asyncOpen(concurrent_listener);
@@ -422,7 +437,11 @@ async function test_http2_concurrent(concurrent_channels, serverPort) {
return p;
}
-async function test_http2_concurrent_post(concurrent_channels, serverPort) {
+async function test_http2_concurrent_post(
+ concurrent_channels,
+ serverPort,
+ origin = "localhost"
+) {
return new Promise(resolve => {
var concurrent_listener = new Http2ConcurrentListener();
concurrent_listener.finish = resolve;
@@ -435,7 +454,7 @@ async function test_http2_concurrent_post(concurrent_channels, serverPort) {
for (var i = 0; i < concurrent_listener.target; i++) {
concurrent_channels[i] = makeHTTPChannel(
- `https://localhost:${serverPort}/750msPost`
+ `https://${origin}:${serverPort}/750msPost`
);
concurrent_channels[i].loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
@@ -473,8 +492,8 @@ async function test_http2_multiplex(serverPort) {
}
// Test to make sure we gateway non-standard headers properly
-async function test_http2_header(serverPort) {
- let chan = makeHTTPChannel(`https://localhost:${serverPort}/header`);
+async function test_http2_header(serverPort, origin = "localhost") {
+ let chan = makeHTTPChannel(`https://${origin}:${serverPort}/header`);
let hvalue = "Headers are fun";
chan.setRequestHeader("X-Test-Header", hvalue, false);
return new Promise(resolve => {
@@ -489,22 +508,26 @@ async function test_http2_header(serverPort) {
}
// Test to make sure headers with invalid characters in the name are rejected
-async function test_http2_invalid_response_header(serverPort, invalid_kind) {
+async function test_http2_invalid_response_header(
+ serverPort,
+ invalid_kind,
+ origin = "localhost"
+) {
return new Promise(resolve => {
var listener = new Http2CheckListener();
listener.finish = resolve;
listener.shouldSucceed = false;
var chan = makeHTTPChannel(
- `https://localhost:${serverPort}/invalid_response_header/${invalid_kind}`
+ `https://${origin}:${serverPort}/invalid_response_header/${invalid_kind}`
);
chan.asyncOpen(listener);
});
}
// Test to make sure cookies are split into separate fields before compression
-async function test_http2_cookie_crumbling(serverPort) {
+async function test_http2_cookie_crumbling(serverPort, origin = "localhost") {
var chan = makeHTTPChannel(
- `https://localhost:${serverPort}/cookie_crumbling`
+ `https://${origin}:${serverPort}/cookie_crumbling`
);
var cookiesSent = ["a=b", "c=d01234567890123456789", "e=f"].sort();
chan.setRequestHeader("Cookie", cookiesSent.join("; "), false);
@@ -532,8 +555,8 @@ async function test_http2_cookie_crumbling(serverPort) {
// this is a basic test where the server sends a simple document with 2 header
// blocks. bug 1027364
-async function test_http2_doubleheader(serverPort) {
- var chan = makeHTTPChannel(`https://localhost:${serverPort}/doubleheader`);
+async function test_http2_doubleheader(serverPort, origin = "localhost") {
+ var chan = makeHTTPChannel(`https://${origin}:${serverPort}/doubleheader`);
return new Promise(resolve => {
var listener = new Http2CheckListener();
listener.finish = resolve;
@@ -542,8 +565,8 @@ async function test_http2_doubleheader(serverPort) {
}
// Make sure we handle GETs that cover more than 2 frames properly
-async function test_http2_big(serverPort) {
- var chan = makeHTTPChannel(`https://localhost:${serverPort}/big`);
+async function test_http2_big(serverPort, origin = "localhost") {
+ var chan = makeHTTPChannel(`https://${origin}:${serverPort}/big`);
return new Promise(resolve => {
var listener = new Http2BigListener();
listener.finish = resolve;
@@ -551,8 +574,8 @@ async function test_http2_big(serverPort) {
});
}
-async function test_http2_huge_suspended(serverPort) {
- var chan = makeHTTPChannel(`https://localhost:${serverPort}/huge`);
+async function test_http2_huge_suspended(serverPort, origin = "localhost") {
+ var chan = makeHTTPChannel(`https://${origin}:${serverPort}/huge`);
return new Promise(resolve => {
var listener = new Http2HugeSuspendedListener();
listener.finish = resolve;
@@ -578,8 +601,8 @@ function do_post(content, chan, listener, method) {
}
// Make sure we can do a simple POST
-async function test_http2_post(serverPort) {
- var chan = makeHTTPChannel(`https://localhost:${serverPort}/post`);
+async function test_http2_post(serverPort, origin = "localhost") {
+ var chan = makeHTTPChannel(`https://${origin}:${serverPort}/post`);
var p = new Promise(resolve => {
var listener = new Http2PostListener(md5s[0]);
listener.finish = resolve;
@@ -588,8 +611,8 @@ async function test_http2_post(serverPort) {
return p;
}
-async function test_http2_empty_post(serverPort) {
- var chan = makeHTTPChannel(`https://localhost:${serverPort}/post`);
+async function test_http2_empty_post(serverPort, origin = "localhost") {
+ var chan = makeHTTPChannel(`https://${origin}:${serverPort}/post`);
var p = new Promise(resolve => {
var listener = new Http2PostListener("0");
listener.finish = resolve;
@@ -599,8 +622,8 @@ async function test_http2_empty_post(serverPort) {
}
// Make sure we can do a simple PATCH
-async function test_http2_patch(serverPort) {
- var chan = makeHTTPChannel(`https://localhost:${serverPort}/patch`);
+async function test_http2_patch(serverPort, origin = "localhost") {
+ var chan = makeHTTPChannel(`https://${origin}:${serverPort}/patch`);
return new Promise(resolve => {
var listener = new Http2PostListener(md5s[0]);
listener.finish = resolve;
@@ -609,8 +632,8 @@ async function test_http2_patch(serverPort) {
}
// Make sure we can do a POST that covers more than 2 frames
-async function test_http2_post_big(serverPort) {
- var chan = makeHTTPChannel(`https://localhost:${serverPort}/post`);
+async function test_http2_post_big(serverPort, origin = "localhost") {
+ var chan = makeHTTPChannel(`https://${origin}:${serverPort}/post`);
return new Promise(resolve => {
var listener = new Http2PostListener(md5s[1]);
listener.finish = resolve;
@@ -764,13 +787,13 @@ WrongSuiteListener.prototype.onStopRequest = function (request, status) {
// test that we use h1 without the mandatory cipher suite available when
// offering at most tls1.2
-async function test_http2_wrongsuite_tls12(serverPort) {
+async function test_http2_wrongsuite_tls12(serverPort, origin = "localhost") {
Services.prefs.setBoolPref(
"security.ssl3.ecdhe_rsa_aes_128_gcm_sha256",
false
);
Services.prefs.setIntPref("security.tls.version.max", 3);
- var chan = makeHTTPChannel(`https://localhost:${serverPort}/wrongsuite`);
+ var chan = makeHTTPChannel(`https://${origin}:${serverPort}/wrongsuite`);
chan.loadFlags =
Ci.nsIRequest.LOAD_FRESH_CONNECTION |
Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
@@ -783,12 +806,12 @@ async function test_http2_wrongsuite_tls12(serverPort) {
// test that we use h2 when offering tls1.3 or higher regardless of if the
// mandatory cipher suite is available
-async function test_http2_wrongsuite_tls13(serverPort) {
+async function test_http2_wrongsuite_tls13(serverPort, origin = "localhost") {
Services.prefs.setBoolPref(
"security.ssl3.ecdhe_rsa_aes_128_gcm_sha256",
false
);
- var chan = makeHTTPChannel(`https://localhost:${serverPort}/wrongsuite`);
+ var chan = makeHTTPChannel(`https://${origin}:${serverPort}/wrongsuite`);
chan.loadFlags =
Ci.nsIRequest.LOAD_FRESH_CONNECTION |
Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
@@ -800,9 +823,9 @@ async function test_http2_wrongsuite_tls13(serverPort) {
});
}
-async function test_http2_h11required_stream(serverPort) {
+async function test_http2_h11required_stream(serverPort, origin = "localhost") {
var chan = makeHTTPChannel(
- `https://localhost:${serverPort}/h11required_stream`
+ `https://${origin}:${serverPort}/h11required_stream`
);
return new Promise(resolve => {
var listener = new Http2CheckListener();
@@ -829,9 +852,12 @@ H11RequiredSessionListener.prototype.onStopRequest = function (request) {
this.finish({ httpProxyConnectResponseCode });
};
-async function test_http2_h11required_session(serverPort) {
+async function test_http2_h11required_session(
+ serverPort,
+ origin = "localhost"
+) {
var chan = makeHTTPChannel(
- `https://localhost:${serverPort}/h11required_session`
+ `https://${origin}:${serverPort}/h11required_session`
);
return new Promise(resolve => {
var listener = new H11RequiredSessionListener();
@@ -841,8 +867,8 @@ async function test_http2_h11required_session(serverPort) {
});
}
-async function test_http2_retry_rst(serverPort) {
- var chan = makeHTTPChannel(`https://localhost:${serverPort}/rstonce`);
+async function test_http2_retry_rst(serverPort, origin = "localhost") {
+ var chan = makeHTTPChannel(`https://${origin}:${serverPort}/rstonce`);
return new Promise(resolve => {
var listener = new Http2CheckListener();
listener.finish = resolve;
@@ -852,16 +878,18 @@ async function test_http2_retry_rst(serverPort) {
async function test_http2_continuations_over_max_response_limit(
loadGroup,
- serverPort
+ serverPort,
+ origin = "localhost"
) {
var chan = makeHTTPChannel(
- `https://localhost:${serverPort}/hugecontinuedheaders?size=385`
+ `https://${origin}:${serverPort}/hugecontinuedheaders?size=385`
);
chan.loadGroup = loadGroup;
return new Promise(resolve => {
var listener = new Http2CheckListener();
listener.finish = resolve;
listener.shouldSucceed = false;
+ listener.noResponseStatus = true;
chan.asyncOpen(listener);
});
}
@@ -892,7 +920,7 @@ Http2IllegalHpackListener.prototype.shouldGoAway = false;
Http2IllegalHpackListener.prototype.onStopRequest = function () {
var chan = makeHTTPChannel(
- `https://localhost:${this.serverPort}/illegalhpack_validate`
+ `https://${this.origin}:${this.serverPort}/illegalhpack_validate`
);
var listener = new Http2IllegalHpackValidationListener();
listener.finish = this.finish;
@@ -900,36 +928,42 @@ Http2IllegalHpackListener.prototype.onStopRequest = function () {
chan.asyncOpen(listener);
};
-async function test_http2_illegalhpacksoft(serverPort) {
+async function test_http2_illegalhpacksoft(serverPort, origin = "localhost") {
var chan = makeHTTPChannel(
- `https://localhost:${serverPort}/illegalhpacksoft`
+ `https://${origin}:${serverPort}/illegalhpacksoft`
);
return new Promise(resolve => {
var listener = new Http2IllegalHpackListener();
listener.finish = resolve;
listener.serverPort = serverPort;
+ listener.origin = origin;
listener.shouldGoAway = false;
listener.shouldSucceed = false;
chan.asyncOpen(listener);
});
}
-async function test_http2_illegalhpackhard(serverPort) {
+async function test_http2_illegalhpackhard(serverPort, origin = "localhost") {
var chan = makeHTTPChannel(
- `https://localhost:${serverPort}/illegalhpackhard`
+ `https://${origin}:${serverPort}/illegalhpackhard`
);
return new Promise(resolve => {
var listener = new Http2IllegalHpackListener();
listener.finish = resolve;
listener.serverPort = serverPort;
+ listener.origin = origin;
listener.shouldGoAway = true;
listener.shouldSucceed = false;
chan.asyncOpen(listener);
});
}
-async function test_http2_folded_header(loadGroup, serverPort) {
- var chan = makeHTTPChannel(`https://localhost:${serverPort}/foldedheader`);
+async function test_http2_folded_header(
+ loadGroup,
+ serverPort,
+ origin = "localhost"
+) {
+ var chan = makeHTTPChannel(`https://${origin}:${serverPort}/foldedheader`);
chan.loadGroup = loadGroup;
return new Promise(resolve => {
var listener = new Http2CheckListener();
@@ -939,8 +973,8 @@ async function test_http2_folded_header(loadGroup, serverPort) {
});
}
-async function test_http2_empty_data(serverPort) {
- var chan = makeHTTPChannel(`https://localhost:${serverPort}/emptydata`);
+async function test_http2_empty_data(serverPort, origin = "localhost") {
+ var chan = makeHTTPChannel(`https://${origin}:${serverPort}/emptydata`);
return new Promise(resolve => {
var listener = new Http2CheckListener();
listener.finish = resolve;
@@ -948,8 +982,8 @@ async function test_http2_empty_data(serverPort) {
});
}
-async function test_http2_status_phrase(serverPort) {
- var chan = makeHTTPChannel(`https://localhost:${serverPort}/statusphrase`);
+async function test_http2_status_phrase(serverPort, origin = "localhost") {
+ var chan = makeHTTPChannel(`https://${origin}:${serverPort}/statusphrase`);
return new Promise(resolve => {
var listener = new Http2CheckListener();
listener.finish = resolve;
diff --git a/netwerk/test/unit/test_http2_with_http3_proxy.js b/netwerk/test/unit/test_http2_with_http3_proxy.js
@@ -0,0 +1,344 @@
+// test HTTP/2 with a HTTP/3 prooxy
+
+"use strict";
+
+/* import-globals-from http2_test_common.js */
+
+const { Http3ProxyFilter, NodeHTTP2ProxyServer } = ChromeUtils.importESModule(
+ "resource://testing-common/NodeServer.sys.mjs"
+);
+
+let concurrent_channels = [];
+let loadGroup;
+let serverPort;
+let pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+let proxyHost;
+let proxyPort;
+let proxyFilter;
+
+add_setup(async function setup() {
+ serverPort = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(serverPort, null);
+ dump("using port " + serverPort + "\n");
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+ addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
+
+ Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setBoolPref("network.http.http2.enabled", true);
+ Services.prefs.setBoolPref("network.http.altsvc.enabled", true);
+ Services.prefs.setBoolPref("network.http.altsvc.oe", true);
+ Services.prefs.setCharPref(
+ "network.dns.localDomains",
+ "foo.example.com, alt1.example.com, bar.example.com"
+ );
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(
+ Ci.nsILoadGroup
+ );
+
+ Services.prefs.setStringPref(
+ "services.settings.server",
+ `data:,#remote-settings-dummy/v1`
+ );
+
+ proxyHost = "alt1.example.com";
+ proxyPort = (await create_masque_proxy_server()).masqueProxyPort;
+ Assert.notEqual(proxyPort, null);
+ Assert.notEqual(proxyPort, "");
+ proxyFilter = new Http3ProxyFilter(
+ proxyHost,
+ proxyPort,
+ 0,
+ "/.well-known/masque/udp/{target_host}/{target_port}/",
+ ""
+ );
+ pps.registerFilter(proxyFilter, 10);
+
+ let h2Proxy = new NodeHTTP2ProxyServer();
+ await h2Proxy.startWithoutProxyFilter(proxyPort);
+ Assert.equal(proxyPort, h2Proxy.port());
+
+ registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.http.http3.enable");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+ Services.prefs.clearUserPref("network.http.altsvc.oe");
+ Services.prefs.clearUserPref(
+ "network.http.http3.alt-svc-mapping-for-testing"
+ );
+ await h2Proxy.stop();
+ pps.unregisterFilter(proxyFilter);
+ });
+});
+
+// hack - the header test resets the multiplex object on the server,
+// so make sure header is always run before the multiplex test.
+//
+// make sure post_big runs first to test race condition in restarting
+// a stalled stream when a SETTINGS frame arrives
+add_task(async function do_test_http2_post_big() {
+ const { httpProxyConnectResponseCode } = await test_http2_post_big(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_basic() {
+ const { httpProxyConnectResponseCode } = await test_http2_basic(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_concurrent() {
+ const { httpProxyConnectResponseCode } = await test_http2_concurrent(
+ concurrent_channels,
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_concurrent_post() {
+ const { httpProxyConnectResponseCode } = await test_http2_concurrent_post(
+ concurrent_channels,
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_basic_unblocked_dep() {
+ const { httpProxyConnectResponseCode } = await test_http2_basic_unblocked_dep(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_doubleheader() {
+ const { httpProxyConnectResponseCode } = await test_http2_doubleheader(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_xhr() {
+ await test_http2_xhr(serverPort, "foo.example.com");
+});
+
+add_task(async function do_test_http2_header() {
+ const { httpProxyConnectResponseCode } = await test_http2_header(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_invalid_response_header_name_spaces() {
+ const { httpProxyConnectResponseCode } =
+ await test_http2_invalid_response_header(
+ serverPort,
+ "name_spaces",
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(
+ async function do_test_http2_invalid_response_header_value_line_feed() {
+ const { httpProxyConnectResponseCode } =
+ await test_http2_invalid_response_header(
+ serverPort,
+ "value_line_feed",
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+ }
+);
+
+add_task(
+ async function do_test_http2_invalid_response_header_value_carriage_return() {
+ const { httpProxyConnectResponseCode } =
+ await test_http2_invalid_response_header(
+ serverPort,
+ "value_carriage_return",
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+ }
+);
+
+add_task(async function do_test_http2_invalid_response_header_value_null() {
+ const { httpProxyConnectResponseCode } =
+ await test_http2_invalid_response_header(
+ serverPort,
+ "value_null",
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_cookie_crumbling() {
+ const { httpProxyConnectResponseCode } = await test_http2_cookie_crumbling(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_multiplex() {
+ var values = await test_http2_multiplex(serverPort, "foo.example.com");
+ Assert.equal(values[0].httpProxyConnectResponseCode, 200);
+ Assert.equal(values[1].httpProxyConnectResponseCode, 200);
+ Assert.notEqual(values[0].streamID, values[1].streamID);
+});
+
+add_task(async function do_test_http2_big() {
+ const { httpProxyConnectResponseCode } = await test_http2_big(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_huge_suspended() {
+ const { httpProxyConnectResponseCode } = await test_http2_huge_suspended(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_post() {
+ const { httpProxyConnectResponseCode } = await test_http2_post(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_empty_post() {
+ const { httpProxyConnectResponseCode } = await test_http2_empty_post(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_patch() {
+ const { httpProxyConnectResponseCode } = await test_http2_patch(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_blocking_download() {
+ const { httpProxyConnectResponseCode } = await test_http2_blocking_download(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_illegalhpacksoft() {
+ const { httpProxyConnectResponseCode } = await test_http2_illegalhpacksoft(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_illegalhpackhard() {
+ const { httpProxyConnectResponseCode } = await test_http2_illegalhpackhard(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_folded_header() {
+ const { httpProxyConnectResponseCode } = await test_http2_folded_header(
+ loadGroup,
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_empty_data() {
+ const { httpProxyConnectResponseCode } = await test_http2_empty_data(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_status_phrase() {
+ const { httpProxyConnectResponseCode } = await test_http2_status_phrase(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_h11required_stream() {
+ // Add new tests above here - best to add new tests before h1
+ // streams get too involved
+ // These next two must always come in this order
+ const { httpProxyConnectResponseCode } = await test_http2_h11required_stream(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_h11required_session() {
+ const { httpProxyConnectResponseCode } = await test_http2_h11required_session(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_retry_rst() {
+ const { httpProxyConnectResponseCode } = await test_http2_retry_rst(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_wrongsuite_tls13() {
+ const { httpProxyConnectResponseCode } = await test_http2_wrongsuite_tls13(
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
+
+add_task(async function do_test_http2_continuations_over_max_response_limit() {
+ const { httpProxyConnectResponseCode } =
+ await test_http2_continuations_over_max_response_limit(
+ loadGroup,
+ serverPort,
+ "foo.example.com"
+ );
+ Assert.equal(httpProxyConnectResponseCode, 200);
+});
diff --git a/netwerk/test/unit/test_servers.js b/netwerk/test/unit/test_servers.js
@@ -305,3 +305,61 @@ add_task(async function test_proxy_with_redirects() {
await proxy.stop();
}
});
+
+add_task(async function test_async_event() {
+ let server = new NodeHTTP2Server();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+
+ await server.execute(`new Promise(r => setTimeout(r, 500))`);
+
+ await server.stop();
+});
+
+add_task(async function test_async_state_management() {
+ let server = new NodeHTTP2Server();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+
+ await server.execute(`global.asyncResults = [];`);
+
+ await server.execute(`
+ global.asyncCounter = 0;
+ global.performAsyncOperation = function(delay, value) {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ global.asyncCounter++;
+ global.asyncResults.push({ counter: global.asyncCounter, value });
+ resolve({ counter: global.asyncCounter, value });
+ }, delay);
+ });
+ };
+ `);
+
+ let op1 = server.execute(`performAsyncOperation(100, "first")`);
+ let op2 = server.execute(`performAsyncOperation(50, "second")`);
+
+ let result1 = await op1;
+ let result2 = await op2;
+ // This ran after 100 ms, so it comes in second
+ equal(result1.counter, 2);
+ equal(result1.value, "first");
+
+ // this rand after 50 ms, so it comes in first.
+ equal(result2.counter, 1);
+ equal(result2.value, "second");
+
+ let results = await server.execute(`global.asyncResults`);
+ equal(results.length, 2);
+ equal(results[0].value, "second");
+ equal(results[1].value, "first");
+
+ let counter = await server.execute(`global.asyncCounter`);
+ equal(counter, 2);
+
+ await server.stop();
+});
diff --git a/netwerk/test/unit/xpcshell.toml b/netwerk/test/unit/xpcshell.toml
@@ -709,6 +709,16 @@ skip-if = ["os == 'android'"]
run-sequentially = ["true"] # node server exceptions dont replay well
head = "head_channels.js head_cache.js head_cookies.js head_trr.js head_http3.js http2_test_common.js"
+["test_http2_with_http3_proxy.js"]
+run-sequentially = ["true"] # node server exceptions dont replay well
+head = "head_channels.js head_cache.js head_cookies.js head_trr.js head_http3.js http2_test_common.js"
+skip-if = [
+ "os == 'android' && os_version == '14' && processor == 'x86_64'", # Bug 1982955
+ "os == 'win' && os_version == '10.2009' && processor == 'x86_64'", # Bug 1807931
+ "os == 'win' && os_version == '11.26100' && processor == 'x86'", # Bug 1807931
+ "os == 'win' && os_version == '11.26100' && processor == 'x86_64' && msix", # Bug 1807931
+]
+
["test_http3.js"]
head = "head_cookies.js head_channels.js head_cache.js head_http3.js http3_common.js"
run-sequentially = ["true"] # http3server
diff --git a/testing/xpcshell/moz-http2/moz-http2-child.js b/testing/xpcshell/moz-http2/moz-http2-child.js
@@ -4,8 +4,8 @@
/* eslint-env node */
-function sendBackResponse(evalResult, e) {
- const output = { result: evalResult, error: "", errorStack: "" };
+function sendBackResponse(messageId, evalResult, e) {
+ const output = { result: evalResult, error: "", errorStack: "", messageId };
if (e) {
output.error = e.toString();
output.errorStack = e.stack;
@@ -15,19 +15,20 @@ function sendBackResponse(evalResult, e) {
process.on("message", msg => {
const code = msg.code;
+ const messageId = msg.messageId;
let evalResult = null;
try {
// eslint-disable-next-line no-eval
evalResult = eval(code);
if (evalResult instanceof Promise) {
evalResult
- .then(x => sendBackResponse(x))
- .catch(e => sendBackResponse(undefined, e));
+ .then(x => sendBackResponse(messageId, x))
+ .catch(e => sendBackResponse(messageId, undefined, e));
return;
}
} catch (e) {
- sendBackResponse(undefined, e);
+ sendBackResponse(messageId, undefined, e);
return;
}
- sendBackResponse(evalResult);
+ sendBackResponse(messageId, evalResult);
});
diff --git a/testing/xpcshell/moz-http2/moz-http2.js b/testing/xpcshell/moz-http2/moz-http2.js
@@ -1486,10 +1486,10 @@ let httpServer = http.createServer((req, res) => {
return;
}
+ let messageId = makeid(6);
new Promise((resolve, reject) => {
- forked.resolve = resolve;
- forked.reject = reject;
- forked.send({ code });
+ forked.messageHandlers[messageId] = { resolve, reject };
+ forked.send({ code, messageId });
})
.then(x => sendBackResponse(x))
.catch(e => computeAndSendBackResponse(undefined, e));
@@ -1538,11 +1538,13 @@ function forkProcess() {
function forkProcessInternal(forked) {
let id = makeid(6);
forked.errors = "";
+ forked.messageHandlers = {};
globalObjects[id] = forked;
forked.on("message", msg => {
- if (forked.resolve) {
- forked.resolve(msg);
- forked.resolve = null;
+ if (msg.messageId && forked.messageHandlers[msg.messageId]) {
+ let handler = forked.messageHandlers[msg.messageId];
+ delete forked.messageHandlers[msg.messageId];
+ handler.resolve(msg);
} else {
console.log(
`forked process without handler sent: ${JSON.stringify(msg)}`
@@ -1561,22 +1563,30 @@ function forkProcessInternal(forked) {
return;
}
- if (!forked.reject) {
+ let errorMsg = `child process exit closing code: ${code} signal: ${signal}`;
+ if (forked.errors != "") {
+ errorMsg = forked.errors;
+ forked.errors = "";
+ }
+
+ // Handle /kill/ case where forked.reject is set
+ if (forked.reject) {
+ forked.reject(new Error(errorMsg));
+ forked.reject = null;
+ forked.resolve = null;
+ }
+
+ if (Object.keys(forked.messageHandlers).length === 0) {
console.log(
`child process ${id} closing code: ${code} signal: ${signal}`
);
return;
}
- if (forked.errors != "") {
- forked.reject(forked.errors);
- forked.errors = "";
- forked.reject = null;
- return;
+ for (let messageId in forked.messageHandlers) {
+ forked.messageHandlers[messageId].reject(new Error(errorMsg));
}
-
- forked.reject(`child process exit closing code: ${code} signal: ${signal}`);
- forked.reject = null;
+ forked.messageHandlers = {};
};
forked.on("error", exitFunction);