Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* 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(
);
function makeChan(uri) {
let chan = NetUtil.newChannel({
uri,
loadUsingSystemPrincipal: true,
}).QueryInterface(Ci.nsIHttpChannel);
chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
return chan;
}
add_setup(async function setup() {
// See Bug 1878505
Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
registerCleanupFunction(async () => {
Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
});
});
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 = 0x4b0007;
// const NS_NET_STATUS_CONNECTED_TO = 0x4b0004;
// const NS_NET_STATUS_SENDING_TO = 0x4b0005;
const NS_NET_STATUS_WAITING_FOR = 0x4b000a; // 2152398858
const NS_NET_STATUS_RECEIVING_FROM = 0x4b0006;
// const NS_NET_STATUS_TLS_HANDSHAKE_STARTING = 0x4b000c; // 2152398860
// const NS_NET_STATUS_TLS_HANDSHAKE_ENDED = 0x4b000d; // 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() {},
onStatus(request, status) {
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) {
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();
}
}
);
});