summaryrefslogtreecommitdiffstats
path: root/netwerk/test/unit/test_proxy_cancel.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netwerk/test/unit/test_proxy_cancel.js397
1 files changed, 397 insertions, 0 deletions
diff --git a/netwerk/test/unit/test_proxy_cancel.js b/netwerk/test/unit/test_proxy_cancel.js
new file mode 100644
index 0000000000..2ad75898b0
--- /dev/null
+++ b/netwerk/test/unit/test_proxy_cancel.js
@@ -0,0 +1,397 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* globals setTimeout */
+
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+add_task(async function test_cancel_after_asyncOpen() {
+ 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");
+
+ let proxies = [
+ NodeHTTPProxyServer,
+ NodeHTTPSProxyServer,
+ NodeHTTP2ProxyServer,
+ ];
+ for (let p of proxies) {
+ let proxy = new p();
+ await proxy.start();
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ });
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ async server => {
+ info(`Testing ${p.name} with ${server.constructor.name}`);
+ await server.execute(
+ `global.server_name = "${server.constructor.name}";`
+ );
+
+ await server.registerPathHandler("/test", (req, resp) => {
+ resp.writeHead(200);
+ resp.end(global.server_name);
+ });
+
+ let chan = makeChan(`${server.origin()}/test`);
+ let openPromise = new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ (req, buff) => resolve({ req, buff }),
+ null,
+ CL_EXPECT_FAILURE
+ )
+ );
+ });
+ chan.cancel(Cr.NS_ERROR_ABORT);
+ let { req } = await openPromise;
+ Assert.equal(req.status, Cr.NS_ERROR_ABORT);
+ }
+ );
+ await proxy.stop();
+ }
+});
+
+// const NS_NET_STATUS_CONNECTING_TO = 0x804b0007;
+// const NS_NET_STATUS_CONNECTED_TO = 0x804b0004;
+// const NS_NET_STATUS_SENDING_TO = 0x804b0005;
+const NS_NET_STATUS_WAITING_FOR = 0x804b000a; // 2152398858
+const NS_NET_STATUS_RECEIVING_FROM = 0x804b0006;
+// const NS_NET_STATUS_TLS_HANDSHAKE_STARTING = 0x804b000c; // 2152398860
+// const NS_NET_STATUS_TLS_HANDSHAKE_ENDED = 0x804b000d; // 2152398861
+
+add_task(async function test_cancel_after_connect_http2proxy() {
+ 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");
+
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ async server => {
+ // Set up a proxy for each server to make sure proxy state is clean
+ // for each test.
+ let proxy = new NodeHTTP2ProxyServer();
+ await proxy.start();
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ });
+ await proxy.execute(`
+ global.session_counter = 0;
+ global.proxy.on("session", () => {
+ global.session_counter++;
+ });
+ `);
+
+ info(`Testing ${proxy.constructor.name} with ${server.constructor.name}`);
+ await server.execute(
+ `global.server_name = "${server.constructor.name}";`
+ );
+
+ await server.registerPathHandler("/test", (req, resp) => {
+ global.reqCount = (global.reqCount || 0) + 1;
+ resp.writeHead(200);
+ resp.end(global.server_name);
+ });
+
+ let chan = makeChan(`${server.origin()}/test`);
+ chan.notificationCallbacks = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIInterfaceRequestor",
+ "nsIProgressEventSink",
+ ]),
+
+ getInterface(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ onProgress(request, progress, progressMax) {},
+ onStatus(request, status, statusArg) {
+ info(`status = ${status}`);
+ // XXX(valentin): Is this the best status to be cancelling?
+ if (status == NS_NET_STATUS_WAITING_FOR) {
+ info("cancelling connected channel");
+ chan.cancel(Cr.NS_ERROR_ABORT);
+ }
+ },
+ };
+ let openPromise = new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ (req, buff) => resolve({ req, buff }),
+ null,
+ CL_EXPECT_FAILURE
+ )
+ );
+ });
+ let { req } = await openPromise;
+ Assert.equal(req.status, Cr.NS_ERROR_ABORT);
+
+ // Since we're cancelling just after connect, we'd expect that no
+ // requests are actually registered. But because we're cancelling on the
+ // main thread, and the request is being performed on the socket thread,
+ // it might actually reach the server, especially in chaos test mode.
+ // Assert.equal(
+ // await server.execute(`global.reqCount || 0`),
+ // 0,
+ // `No requests should have been made at this point`
+ // );
+ Assert.equal(await proxy.execute(`global.session_counter`), 1);
+
+ chan = makeChan(`${server.origin()}/test`);
+ await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ // eslint-disable-next-line no-shadow
+ (req, buff) => resolve({ req, buff }),
+ null,
+ CL_ALLOW_UNKNOWN_CL
+ )
+ );
+ });
+
+ // Check that there's still only one session.
+ Assert.equal(await proxy.execute(`global.session_counter`), 1);
+ await proxy.stop();
+ }
+ );
+});
+
+add_task(async function test_cancel_after_sending_request() {
+ 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");
+
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ async server => {
+ let proxies = [
+ NodeHTTPProxyServer,
+ NodeHTTPSProxyServer,
+ NodeHTTP2ProxyServer,
+ ];
+ for (let p of proxies) {
+ let proxy = new p();
+ await proxy.start();
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ });
+
+ await proxy.execute(`
+ global.session_counter = 0;
+ global.proxy.on("session", () => {
+ global.session_counter++;
+ });
+ `);
+ info(`Testing ${p.name} with ${server.constructor.name}`);
+ await server.execute(
+ `global.server_name = "${server.constructor.name}";`
+ );
+
+ await server.registerPathHandler("/test", (req, resp) => {
+ // Here we simmulate a slow response to give the test time to
+ // cancel the channel before receiving the response.
+ global.request_count = (global.request_count || 0) + 1;
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(() => {
+ resp.writeHead(200);
+ resp.end(global.server_name);
+ }, 2000);
+ });
+ await server.registerPathHandler("/instant", (req, resp) => {
+ resp.writeHead(200);
+ resp.end(global.server_name);
+ });
+
+ // It seems proxy.on("session") only gets emitted after a full request.
+ // So we first load a simple request, then the long lasting request
+ // that we then cancel before it has the chance to complete.
+ let chan = makeChan(`${server.origin()}/instant`);
+ await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL)
+ );
+ });
+
+ chan = makeChan(`${server.origin()}/test`);
+ let openPromise = new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ (req, buff) => resolve({ req, buff }),
+ null,
+ CL_EXPECT_FAILURE
+ )
+ );
+ });
+ // XXX(valentin) This might be a little racy
+ await TestUtils.waitForCondition(async () => {
+ return (await server.execute("global.request_count")) > 0;
+ });
+
+ chan.cancel(Cr.NS_ERROR_ABORT);
+
+ let { req } = await openPromise;
+ Assert.equal(req.status, Cr.NS_ERROR_ABORT);
+
+ async function checkSessionCounter() {
+ if (p.name == "NodeHTTP2ProxyServer") {
+ Assert.equal(await proxy.execute(`global.session_counter`), 1);
+ }
+ }
+
+ await checkSessionCounter();
+
+ chan = makeChan(`${server.origin()}/instant`);
+ await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ // eslint-disable-next-line no-shadow
+ (req, buff) => resolve({ req, buff }),
+ null,
+ CL_ALLOW_UNKNOWN_CL
+ )
+ );
+ });
+ await checkSessionCounter();
+
+ await proxy.stop();
+ }
+ }
+ );
+});
+
+add_task(async function test_cancel_during_response() {
+ 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");
+
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ async server => {
+ let proxies = [
+ NodeHTTPProxyServer,
+ NodeHTTPSProxyServer,
+ NodeHTTP2ProxyServer,
+ ];
+ for (let p of proxies) {
+ let proxy = new p();
+ await proxy.start();
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ });
+
+ await proxy.execute(`
+ global.session_counter = 0;
+ global.proxy.on("session", () => {
+ global.session_counter++;
+ });
+ `);
+
+ info(`Testing ${p.name} with ${server.constructor.name}`);
+ await server.execute(
+ `global.server_name = "${server.constructor.name}";`
+ );
+
+ await server.registerPathHandler("/test", (req, resp) => {
+ resp.writeHead(200);
+ resp.write("a".repeat(1000));
+ // Here we send the response back in two chunks.
+ // The channel should be cancelled after the first one.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(() => {
+ resp.write("a".repeat(1000));
+ resp.end(global.server_name);
+ }, 2000);
+ });
+ await server.registerPathHandler("/instant", (req, resp) => {
+ resp.writeHead(200);
+ resp.end(global.server_name);
+ });
+
+ let chan = makeChan(`${server.origin()}/test`);
+
+ chan.notificationCallbacks = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIInterfaceRequestor",
+ "nsIProgressEventSink",
+ ]),
+
+ getInterface(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ onProgress(request, progress, progressMax) {
+ info(`progress: ${progress}/${progressMax}`);
+ // Check that we never get more than 1000 bytes.
+ Assert.equal(progress, 1000);
+ },
+ onStatus(request, status, statusArg) {
+ if (status == NS_NET_STATUS_RECEIVING_FROM) {
+ info("cancelling when receiving request");
+ chan.cancel(Cr.NS_ERROR_ABORT);
+ }
+ },
+ };
+
+ let openPromise = new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ (req, buff) => resolve({ req, buff }),
+ null,
+ CL_EXPECT_FAILURE
+ )
+ );
+ });
+
+ let { req } = await openPromise;
+ Assert.equal(req.status, Cr.NS_ERROR_ABORT);
+
+ async function checkSessionCounter() {
+ if (p.name == "NodeHTTP2ProxyServer") {
+ Assert.equal(await proxy.execute(`global.session_counter`), 1);
+ }
+ }
+
+ await checkSessionCounter();
+
+ chan = makeChan(`${server.origin()}/instant`);
+ await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ // eslint-disable-next-line no-shadow
+ (req, buff) => resolve({ req, buff }),
+ null,
+ CL_ALLOW_UNKNOWN_CL
+ )
+ );
+ });
+
+ await checkSessionCounter();
+
+ await proxy.stop();
+ }
+ }
+ );
+});