mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-05 18:58:33 +02:00
This changes where the IsUpgradeDowngradeEndlessLoop check triggers. Before this patch, it triggered during the redirect caused by the https upgrade. With this patch, it triggers during the downgrade for http redirects. META and JS redirect are still detected during upgrade. This should be fixed as a follow up (See Bug 1896691). Downgrade in this context means same url, except with the scheme http instead of https. Different query parameters normally lead to different responses by web servers. Don't consider the '#ref' part of the uri, because it doesn't get send to the server and therefore can't change the server response. We can't use the redirect chain anymore, because the query parameters are trimmed since Bug 1715785. This also removes the config option dom.security.https_only_check_path_upgrade_downgrade_endless_loop, because it adds unnecessary complexity. Removing it for this patch is easier. https-only, https-first and httpssvc_https_upgrade tests had to be modified, because they depended on the incorrect handling of query strings in loop detection. Original Revision: https://phabricator.services.mozilla.com/D193672 Differential Revision: https://phabricator.services.mozilla.com/D214977
1992 lines
58 KiB
JavaScript
1992 lines
58 KiB
JavaScript
/* 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/. */
|
|
|
|
// This module is the stateful server side of test_http2.js and is meant
|
|
// to have node be restarted in between each invocation
|
|
|
|
/* eslint-env node */
|
|
|
|
var node_http2_root = "../node-http2";
|
|
if (process.env.NODE_HTTP2_ROOT) {
|
|
node_http2_root = process.env.NODE_HTTP2_ROOT;
|
|
}
|
|
var http2 = require(node_http2_root);
|
|
var fs = require("fs");
|
|
var url = require("url");
|
|
var crypto = require("crypto");
|
|
const dnsPacket = require(`${node_http2_root}/../dns-packet`);
|
|
const ip = require(`${node_http2_root}/../node_ip`);
|
|
const { fork } = require("child_process");
|
|
const { spawn } = require("child_process");
|
|
const path = require("path");
|
|
const zlib = require("zlib");
|
|
|
|
// Hook into the decompression code to log the decompressed name-value pairs
|
|
var compression_module = node_http2_root + "/lib/protocol/compressor";
|
|
var http2_compression = require(compression_module);
|
|
var HeaderSetDecompressor = http2_compression.HeaderSetDecompressor;
|
|
var originalRead = HeaderSetDecompressor.prototype.read;
|
|
var lastDecompressor;
|
|
var decompressedPairs;
|
|
HeaderSetDecompressor.prototype.read = function () {
|
|
if (this != lastDecompressor) {
|
|
lastDecompressor = this;
|
|
decompressedPairs = [];
|
|
}
|
|
var pair = originalRead.apply(this, arguments);
|
|
if (pair) {
|
|
decompressedPairs.push(pair);
|
|
}
|
|
return pair;
|
|
};
|
|
|
|
var connection_module = node_http2_root + "/lib/protocol/connection";
|
|
var http2_connection = require(connection_module);
|
|
var Connection = http2_connection.Connection;
|
|
var originalClose = Connection.prototype.close;
|
|
Connection.prototype.close = function (error, lastId) {
|
|
if (lastId !== undefined) {
|
|
this._lastIncomingStream = lastId;
|
|
}
|
|
|
|
originalClose.apply(this, arguments);
|
|
};
|
|
|
|
var framer_module = node_http2_root + "/lib/protocol/framer";
|
|
var http2_framer = require(framer_module);
|
|
var Serializer = http2_framer.Serializer;
|
|
var originalTransform = Serializer.prototype._transform;
|
|
var newTransform = function (frame) {
|
|
if (frame.type == "DATA") {
|
|
// Insert our empty DATA frame
|
|
const emptyFrame = {};
|
|
emptyFrame.type = "DATA";
|
|
emptyFrame.data = Buffer.alloc(0);
|
|
emptyFrame.flags = [];
|
|
emptyFrame.stream = frame.stream;
|
|
var buffers = [];
|
|
Serializer.DATA(emptyFrame, buffers);
|
|
Serializer.commonHeader(emptyFrame, buffers);
|
|
for (var i = 0; i < buffers.length; i++) {
|
|
this.push(buffers[i]);
|
|
}
|
|
|
|
// Reset to the original version for later uses
|
|
Serializer.prototype._transform = originalTransform;
|
|
}
|
|
originalTransform.apply(this, arguments);
|
|
};
|
|
|
|
function getHttpContent(pathName) {
|
|
var content =
|
|
"<!doctype html>" +
|
|
"<html>" +
|
|
"<head><title>HOORAY!</title></head>" +
|
|
// 'You Win!' used in tests to check we reached this server
|
|
"<body>You Win! (by requesting" +
|
|
pathName +
|
|
")</body>" +
|
|
"</html>";
|
|
return content;
|
|
}
|
|
|
|
function generateContent(size) {
|
|
var content = "";
|
|
for (var i = 0; i < size; i++) {
|
|
content += "0";
|
|
}
|
|
return content;
|
|
}
|
|
|
|
/* This takes care of responding to the multiplexed request for us */
|
|
var m = {
|
|
mp1res: null,
|
|
mp2res: null,
|
|
buf: null,
|
|
mp1start: 0,
|
|
mp2start: 0,
|
|
|
|
checkReady() {
|
|
if (this.mp1res != null && this.mp2res != null) {
|
|
this.buf = generateContent(30 * 1024);
|
|
this.mp1start = 0;
|
|
this.mp2start = 0;
|
|
this.send(this.mp1res, 0);
|
|
setTimeout(this.send.bind(this, this.mp2res, 0), 5);
|
|
}
|
|
},
|
|
|
|
send(res, start) {
|
|
var end = Math.min(start + 1024, this.buf.length);
|
|
var content = this.buf.substring(start, end);
|
|
res.write(content);
|
|
if (end < this.buf.length) {
|
|
setTimeout(this.send.bind(this, res, end), 10);
|
|
} else {
|
|
// Clear these variables so we can run the test again with --verify
|
|
if (res == this.mp1res) {
|
|
this.mp1res = null;
|
|
} else {
|
|
this.mp2res = null;
|
|
}
|
|
res.end();
|
|
}
|
|
},
|
|
};
|
|
|
|
var runlater = function () {};
|
|
runlater.prototype = {
|
|
req: null,
|
|
resp: null,
|
|
fin: true,
|
|
|
|
onTimeout: function onTimeout() {
|
|
this.resp.writeHead(200);
|
|
if (this.fin) {
|
|
this.resp.end("It's all good 750ms.");
|
|
}
|
|
},
|
|
};
|
|
|
|
var runConnectLater = function () {};
|
|
runConnectLater.prototype = {
|
|
req: null,
|
|
resp: null,
|
|
connect: false,
|
|
|
|
onTimeout: function onTimeout() {
|
|
if (this.connect) {
|
|
this.resp.writeHead(200);
|
|
this.connect = true;
|
|
setTimeout(executeRunLaterCatchError, 50, this);
|
|
} else {
|
|
this.resp.end("HTTP/1.1 200\n\r\n\r");
|
|
}
|
|
},
|
|
};
|
|
|
|
var moreData = function () {};
|
|
moreData.prototype = {
|
|
req: null,
|
|
resp: null,
|
|
iter: 3,
|
|
|
|
onTimeout: function onTimeout() {
|
|
// 1mb of data
|
|
const content = generateContent(1024 * 1024);
|
|
this.resp.write(content); // 1mb chunk
|
|
this.iter--;
|
|
if (!this.iter) {
|
|
this.resp.end();
|
|
} else {
|
|
setTimeout(executeRunLater, 1, this);
|
|
}
|
|
},
|
|
};
|
|
|
|
function executeRunLater(arg) {
|
|
arg.onTimeout();
|
|
}
|
|
|
|
function executeRunLaterCatchError(arg) {
|
|
arg.onTimeout();
|
|
}
|
|
|
|
var h11required_conn = null;
|
|
var h11required_header = "yes";
|
|
var didRst = false;
|
|
var rstConnection = null;
|
|
var illegalheader_conn = null;
|
|
|
|
var gDoHPortsLog = [];
|
|
var gDoHNewConnLog = {};
|
|
var gDoHRequestCount = 0;
|
|
|
|
// eslint-disable-next-line complexity
|
|
function handleRequest(req, res) {
|
|
var u = "";
|
|
if (req.url != undefined) {
|
|
u = url.parse(req.url, true);
|
|
}
|
|
var content = getHttpContent(u.pathname);
|
|
var push, push1, push1a, push2, push3;
|
|
|
|
// PushService tests.
|
|
var pushPushServer1, pushPushServer2, pushPushServer3, pushPushServer4;
|
|
|
|
function createCNameContent(payload) {
|
|
let packet = dnsPacket.decode(payload);
|
|
if (
|
|
packet.questions[0].name == "cname.example.com" &&
|
|
packet.questions[0].type == "A"
|
|
) {
|
|
return dnsPacket.encode({
|
|
id: 0,
|
|
type: "response",
|
|
flags: dnsPacket.RECURSION_DESIRED,
|
|
questions: [{ name: packet.questions[0].name, type: "A", class: "IN" }],
|
|
answers: [
|
|
{
|
|
name: packet.questions[0].name,
|
|
ttl: 55,
|
|
type: "CNAME",
|
|
flush: false,
|
|
data: "pointing-elsewhere.example.com",
|
|
},
|
|
],
|
|
});
|
|
}
|
|
if (
|
|
packet.questions[0].name == "pointing-elsewhere.example.com" &&
|
|
packet.questions[0].type == "A"
|
|
) {
|
|
return dnsPacket.encode({
|
|
id: 0,
|
|
type: "response",
|
|
flags: dnsPacket.RECURSION_DESIRED,
|
|
questions: [{ name: packet.questions[0].name, type: "A", class: "IN" }],
|
|
answers: [
|
|
{
|
|
name: packet.questions[0].name,
|
|
ttl: 55,
|
|
type: "A",
|
|
flush: false,
|
|
data: "99.88.77.66",
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
return dnsPacket.encode({
|
|
id: 0,
|
|
type: "response",
|
|
flags: dnsPacket.RECURSION_DESIRED | dnsPacket.rcodes.toRcode("NXDOMAIN"),
|
|
questions: [
|
|
{
|
|
name: packet.questions[0].name,
|
|
type: packet.questions[0].type,
|
|
class: "IN",
|
|
},
|
|
],
|
|
answers: [],
|
|
});
|
|
}
|
|
|
|
function createCNameARecord() {
|
|
// test23 asks for cname-a.example.com
|
|
// this responds with a CNAME to here.example.com *and* an A record
|
|
// for here.example.com
|
|
let rContent;
|
|
|
|
rContent = Buffer.from(
|
|
"0000" +
|
|
"0100" +
|
|
"0001" + // QDCOUNT
|
|
"0002" + // ANCOUNT
|
|
"00000000" + // NSCOUNT + ARCOUNT
|
|
"07636E616D652d61" + // cname-a
|
|
"076578616D706C6503636F6D00" + // .example.com
|
|
"00010001" + // question type (A) + question class (IN)
|
|
// answer record 1
|
|
"C00C" + // name pointer to cname-a.example.com
|
|
"0005" + // type (CNAME)
|
|
"0001" + // class
|
|
"00000037" + // TTL
|
|
"0012" + // RDLENGTH
|
|
"0468657265" + // here
|
|
"076578616D706C6503636F6D00" + // .example.com
|
|
// answer record 2, the A entry for the CNAME above
|
|
"0468657265" + // here
|
|
"076578616D706C6503636F6D00" + // .example.com
|
|
"0001" + // type (A)
|
|
"0001" + // class
|
|
"00000037" + // TTL
|
|
"0004" + // RDLENGTH
|
|
"09080706", // IPv4 address
|
|
"hex"
|
|
);
|
|
|
|
return rContent;
|
|
}
|
|
|
|
function responseType(packet, responseIP) {
|
|
if (
|
|
!!packet.questions.length &&
|
|
packet.questions[0].name == "confirm.example.com" &&
|
|
packet.questions[0].type == "NS"
|
|
) {
|
|
return "NS";
|
|
}
|
|
|
|
return ip.isV4Format(responseIP) ? "A" : "AAAA";
|
|
}
|
|
|
|
function handleAuth() {
|
|
// There's a Set-Cookie: header in the response for "/dns" , which this
|
|
// request subsequently would include if the http channel wasn't
|
|
// anonymous. Thus, if there's a cookie in this request, we know Firefox
|
|
// mishaved. If there's not, we're fine.
|
|
if (req.headers.cookie) {
|
|
res.writeHead(403);
|
|
res.end("cookie for me, not for you");
|
|
return false;
|
|
}
|
|
if (req.headers.authorization != "user:password") {
|
|
res.writeHead(401);
|
|
res.end("bad boy!");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function createDNSAnswer(response, packet, responseIP, requestPayload) {
|
|
// This shuts down the connection so we can test if the client reconnects
|
|
if (packet.questions.length && packet.questions[0].name == "closeme.com") {
|
|
response.stream.connection.close("INTERNAL_ERROR", response.stream.id);
|
|
return null;
|
|
}
|
|
|
|
let answers = [];
|
|
if (packet.questions.length && packet.questions[0].name.endsWith(".pd")) {
|
|
// Bug 1543811: test edns padding extension. Return whether padding was
|
|
// included via the first half of the ip address (1.1 vs 2.2) and the
|
|
// size of the request in the second half of the ip address allowing to
|
|
// verify that the correct amount of padding was added.
|
|
if (
|
|
!!packet.additionals.length &&
|
|
packet.additionals[0].type == "OPT" &&
|
|
packet.additionals[0].options.some(o => o.type === "PADDING")
|
|
) {
|
|
// add padding to the response, because the client must be able ignore it
|
|
answers.push({
|
|
name: ".",
|
|
type: "PADDING",
|
|
data: Buffer.from(
|
|
// PADDING_PADDING_PADDING
|
|
"50414444494e475f50414444494e475f50414444494e47",
|
|
"hex"
|
|
),
|
|
});
|
|
responseIP =
|
|
"1.1." +
|
|
((requestPayload.length >> 8) & 0xff) +
|
|
"." +
|
|
(requestPayload.length & 0xff);
|
|
} else {
|
|
responseIP =
|
|
"2.2." +
|
|
((requestPayload.length >> 8) & 0xff) +
|
|
"." +
|
|
(requestPayload.length & 0xff);
|
|
}
|
|
}
|
|
|
|
if (u.query.corruptedAnswer) {
|
|
// DNS response header is 12 bytes, we check for this minimum length
|
|
// at the start of decoding so this is the simplest way to force
|
|
// a decode error.
|
|
return "\xFF\xFF\xFF\xFF";
|
|
}
|
|
|
|
// Because we send two TRR requests (A and AAAA), skip the first two
|
|
// requests when testing retry.
|
|
if (u.query.retryOnDecodeFailure && gDoHRequestCount < 2) {
|
|
gDoHRequestCount++;
|
|
return "\xFF\xFF\xFF\xFF";
|
|
}
|
|
|
|
function responseData() {
|
|
if (
|
|
!!packet.questions.length &&
|
|
packet.questions[0].name == "confirm.example.com" &&
|
|
packet.questions[0].type == "NS"
|
|
) {
|
|
return "ns.example.com";
|
|
}
|
|
|
|
return responseIP;
|
|
}
|
|
|
|
if (
|
|
responseIP != "none" &&
|
|
responseType(packet, responseIP) == packet.questions[0].type
|
|
) {
|
|
answers.push({
|
|
name: u.query.hostname ? u.query.hostname : packet.questions[0].name,
|
|
ttl: 55,
|
|
type: responseType(packet, responseIP),
|
|
flush: false,
|
|
data: responseData(),
|
|
});
|
|
}
|
|
|
|
// for use with test_dns_by_type_resolve.js
|
|
if (packet.questions[0].type == "TXT") {
|
|
answers.push({
|
|
name: packet.questions[0].name,
|
|
type: packet.questions[0].type,
|
|
ttl: 55,
|
|
class: "IN",
|
|
flush: false,
|
|
data: Buffer.from(
|
|
"62586B67646D39705932556761584D6762586B676347467A63336476636D513D",
|
|
"hex"
|
|
),
|
|
});
|
|
}
|
|
|
|
if (u.query.cnameloop) {
|
|
answers.push({
|
|
name: "cname.example.com",
|
|
type: "CNAME",
|
|
ttl: 55,
|
|
class: "IN",
|
|
flush: false,
|
|
data: "pointing-elsewhere.example.com",
|
|
});
|
|
}
|
|
|
|
if (req.headers["accept-language"] || req.headers["user-agent"]) {
|
|
// If we get this header, don't send back any response. This should
|
|
// cause the tests to fail. This is easier then actually sending back
|
|
// the header value into test_trr.js
|
|
answers = [];
|
|
}
|
|
|
|
let buf = dnsPacket.encode({
|
|
type: "response",
|
|
id: packet.id,
|
|
flags: dnsPacket.RECURSION_DESIRED,
|
|
questions: packet.questions,
|
|
answers,
|
|
});
|
|
|
|
return buf;
|
|
}
|
|
|
|
function getDelayFromPacket(packet, type) {
|
|
let delay = 0;
|
|
if (packet.questions[0].type == "A") {
|
|
delay = parseInt(u.query.delayIPv4);
|
|
} else if (packet.questions[0].type == "AAAA") {
|
|
delay = parseInt(u.query.delayIPv6);
|
|
}
|
|
|
|
if (u.query.slowConfirm && type == "NS") {
|
|
delay += 1000;
|
|
}
|
|
|
|
return delay;
|
|
}
|
|
|
|
function writeDNSResponse(response, buf, delay, contentType) {
|
|
function writeResponse(resp, buffer) {
|
|
resp.setHeader("Set-Cookie", "trackyou=yes; path=/; max-age=100000;");
|
|
resp.setHeader("Content-Type", contentType);
|
|
if (req.headers["accept-encoding"].includes("gzip")) {
|
|
zlib.gzip(buffer, function (err, result) {
|
|
resp.setHeader("Content-Encoding", "gzip");
|
|
resp.setHeader("Content-Length", result.length);
|
|
try {
|
|
resp.writeHead(200);
|
|
resp.end(result);
|
|
} catch (e) {
|
|
// connection was closed by the time we started writing.
|
|
}
|
|
});
|
|
} else {
|
|
const output = Buffer.from(buffer, "utf-8");
|
|
resp.setHeader("Content-Length", output.length);
|
|
try {
|
|
resp.writeHead(200);
|
|
resp.write(output);
|
|
resp.end("");
|
|
} catch (e) {
|
|
// connection was closed by the time we started writing.
|
|
}
|
|
}
|
|
}
|
|
|
|
if (delay) {
|
|
setTimeout(
|
|
arg => {
|
|
writeResponse(arg[0], arg[1]);
|
|
},
|
|
delay,
|
|
[response, buf]
|
|
);
|
|
return;
|
|
}
|
|
|
|
writeResponse(response, buf);
|
|
}
|
|
|
|
if (req.httpVersionMajor === 2) {
|
|
res.setHeader("X-Connection-Http2", "yes");
|
|
res.setHeader("X-Http2-StreamId", "" + req.stream.id);
|
|
} else {
|
|
res.setHeader("X-Connection-Http2", "no");
|
|
}
|
|
|
|
if (u.pathname === "/exit") {
|
|
res.setHeader("Content-Type", "text/plain");
|
|
res.setHeader("Connection", "close");
|
|
res.writeHead(200);
|
|
res.end("ok");
|
|
process.exit();
|
|
}
|
|
|
|
if (req.method == "CONNECT") {
|
|
if (req.headers.host == "illegalhpacksoft.example.com:80") {
|
|
illegalheader_conn = req.stream.connection;
|
|
res.setHeader("Content-Type", "text/html");
|
|
res.setHeader("x-softillegalhpack", "true");
|
|
res.writeHead(200);
|
|
res.end(content);
|
|
return;
|
|
} else if (req.headers.host == "illegalhpackhard.example.com:80") {
|
|
res.setHeader("Content-Type", "text/html");
|
|
res.setHeader("x-hardillegalhpack", "true");
|
|
res.writeHead(200);
|
|
res.end(content);
|
|
return;
|
|
} else if (req.headers.host == "750.example.com:80") {
|
|
// This response will mock a response through a proxy to a HTTP server.
|
|
// After 750ms , a 200 response for the proxy will be sent then
|
|
// after additional 50ms a 200 response for the HTTP GET request.
|
|
let rl = new runConnectLater();
|
|
rl.req = req;
|
|
rl.resp = res;
|
|
setTimeout(executeRunLaterCatchError, 750, rl);
|
|
return;
|
|
} else if (req.headers.host == "h11required.com:80") {
|
|
if (req.httpVersionMajor === 2) {
|
|
res.stream.reset("HTTP_1_1_REQUIRED");
|
|
}
|
|
return;
|
|
}
|
|
} else if (u.pathname === "/750ms") {
|
|
let rl = new runlater();
|
|
rl.req = req;
|
|
rl.resp = res;
|
|
setTimeout(executeRunLater, 750, rl);
|
|
return;
|
|
} else if (u.pathname === "/750msNoData") {
|
|
let rl = new runlater();
|
|
rl.req = req;
|
|
rl.resp = res;
|
|
rl.fin = false;
|
|
setTimeout(executeRunLater, 750, rl);
|
|
return;
|
|
} else if (u.pathname === "/multiplex1" && req.httpVersionMajor === 2) {
|
|
res.setHeader("Content-Type", "text/plain");
|
|
res.writeHead(200);
|
|
m.mp1res = res;
|
|
m.checkReady();
|
|
return;
|
|
} else if (u.pathname === "/multiplex2" && req.httpVersionMajor === 2) {
|
|
res.setHeader("Content-Type", "text/plain");
|
|
res.writeHead(200);
|
|
m.mp2res = res;
|
|
m.checkReady();
|
|
return;
|
|
} else if (u.pathname === "/header") {
|
|
var val = req.headers["x-test-header"];
|
|
if (val) {
|
|
res.setHeader("X-Received-Test-Header", val);
|
|
}
|
|
} else if (u.pathname === "/doubleheader") {
|
|
res.setHeader("Content-Type", "text/html");
|
|
res.writeHead(200);
|
|
res.write(content);
|
|
res.writeHead(200);
|
|
res.end();
|
|
return;
|
|
} else if (u.pathname === "/cookie_crumbling") {
|
|
res.setHeader("X-Received-Header-Pairs", JSON.stringify(decompressedPairs));
|
|
} else if (u.pathname === "/push") {
|
|
push = res.push("/push.js");
|
|
push.writeHead(200, {
|
|
"content-type": "application/javascript",
|
|
pushed: "yes",
|
|
"content-length": 11,
|
|
"X-Connection-Http2": "yes",
|
|
});
|
|
push.end("// comments");
|
|
content = '<head> <script src="push.js"/></head>body text';
|
|
} else if (u.pathname === "/push.js") {
|
|
content = "// comments";
|
|
res.setHeader("pushed", "no");
|
|
} else if (u.pathname === "/push2") {
|
|
push = res.push("/push2.js");
|
|
push.writeHead(200, {
|
|
"content-type": "application/javascript",
|
|
pushed: "yes",
|
|
// no content-length
|
|
"X-Connection-Http2": "yes",
|
|
});
|
|
push.end("// comments");
|
|
content = '<head> <script src="push2.js"/></head>body text';
|
|
} else if (u.pathname === "/push5") {
|
|
push = res.push("/push5.js");
|
|
push.writeHead(200, {
|
|
"content-type": "application/javascript",
|
|
pushed: "yes",
|
|
// no content-length
|
|
"X-Connection-Http2": "yes",
|
|
});
|
|
content = generateContent(1024 * 150);
|
|
push.write(content);
|
|
push.end();
|
|
content = '<head> <script src="push5.js"/></head>body text';
|
|
} else if (u.pathname === "/pushapi1") {
|
|
push1 = res.push({
|
|
hostname: "localhost:" + serverPort,
|
|
port: serverPort,
|
|
path: "/pushapi1/1",
|
|
method: "GET",
|
|
headers: { "x-pushed-request": "true", "x-foo": "bar" },
|
|
});
|
|
push1.writeHead(200, {
|
|
pushed: "yes",
|
|
"content-length": 1,
|
|
subresource: "1",
|
|
"X-Connection-Http2": "yes",
|
|
});
|
|
push1.end("1");
|
|
|
|
push1a = res.push({
|
|
hostname: "localhost:" + serverPort,
|
|
port: serverPort,
|
|
path: "/pushapi1/1",
|
|
method: "GET",
|
|
headers: { "x-foo": "bar", "x-pushed-request": "true" },
|
|
});
|
|
push1a.writeHead(200, {
|
|
pushed: "yes",
|
|
"content-length": 1,
|
|
subresource: "1a",
|
|
"X-Connection-Http2": "yes",
|
|
});
|
|
push1a.end("1");
|
|
|
|
push2 = res.push({
|
|
hostname: "localhost:" + serverPort,
|
|
port: serverPort,
|
|
path: "/pushapi1/2",
|
|
method: "GET",
|
|
headers: { "x-pushed-request": "true" },
|
|
});
|
|
push2.writeHead(200, {
|
|
pushed: "yes",
|
|
subresource: "2",
|
|
"content-length": 1,
|
|
"X-Connection-Http2": "yes",
|
|
});
|
|
push2.end("2");
|
|
|
|
push3 = res.push({
|
|
hostname: "localhost:" + serverPort,
|
|
port: serverPort,
|
|
path: "/pushapi1/3",
|
|
method: "GET",
|
|
headers: { "x-pushed-request": "true", "Accept-Encoding": "br" },
|
|
});
|
|
push3.writeHead(200, {
|
|
pushed: "yes",
|
|
"content-length": 6,
|
|
subresource: "3",
|
|
"content-encoding": "br",
|
|
"X-Connection-Http2": "yes",
|
|
});
|
|
push3.end(Buffer.from([0x8b, 0x00, 0x80, 0x33, 0x0a, 0x03])); // '3\n'
|
|
|
|
content = "0";
|
|
} else if (u.pathname === "/big") {
|
|
content = generateContent(128 * 1024);
|
|
var hash = crypto.createHash("md5");
|
|
hash.update(content);
|
|
let md5 = hash.digest("hex");
|
|
res.setHeader("X-Expected-MD5", md5);
|
|
} else if (u.pathname === "/huge") {
|
|
content = generateContent(1024);
|
|
res.setHeader("Content-Type", "text/plain");
|
|
res.writeHead(200);
|
|
// 1mb of data
|
|
for (let i = 0; i < 1024 * 1; i++) {
|
|
res.write(content); // 1kb chunk
|
|
}
|
|
res.end();
|
|
return;
|
|
} else if (u.pathname === "/post" || u.pathname === "/patch") {
|
|
if (req.method != "POST" && req.method != "PATCH") {
|
|
res.writeHead(405);
|
|
res.end("Unexpected method: " + req.method);
|
|
return;
|
|
}
|
|
|
|
var post_hash = crypto.createHash("md5");
|
|
var received_data = false;
|
|
req.on("data", function receivePostData(chunk) {
|
|
received_data = true;
|
|
post_hash.update(chunk.toString());
|
|
});
|
|
req.on("end", function finishPost() {
|
|
let md5 = received_data ? post_hash.digest("hex") : "0";
|
|
res.setHeader("X-Calculated-MD5", md5);
|
|
res.writeHead(200);
|
|
res.end(content);
|
|
});
|
|
|
|
return;
|
|
} else if (u.pathname === "/750msPost") {
|
|
if (req.method != "POST") {
|
|
res.writeHead(405);
|
|
res.end("Unexpected method: " + req.method);
|
|
return;
|
|
}
|
|
|
|
var accum = 0;
|
|
req.on("data", function receivePostData(chunk) {
|
|
accum += chunk.length;
|
|
});
|
|
req.on("end", function finishPost() {
|
|
res.setHeader("X-Recvd", accum);
|
|
let rl = new runlater();
|
|
rl.req = req;
|
|
rl.resp = res;
|
|
setTimeout(executeRunLater, 750, rl);
|
|
});
|
|
|
|
return;
|
|
} else if (u.pathname === "/h11required_stream") {
|
|
if (req.httpVersionMajor === 2) {
|
|
h11required_conn = req.stream.connection;
|
|
res.stream.reset("HTTP_1_1_REQUIRED");
|
|
return;
|
|
}
|
|
} else if (u.pathname === "/bigdownload") {
|
|
res.setHeader("Content-Type", "text/html");
|
|
res.writeHead(200);
|
|
|
|
let rl = new moreData();
|
|
rl.req = req;
|
|
rl.resp = res;
|
|
setTimeout(executeRunLater, 1, rl);
|
|
return;
|
|
} else if (u.pathname === "/h11required_session") {
|
|
if (req.httpVersionMajor === 2) {
|
|
if (h11required_conn !== req.stream.connection) {
|
|
h11required_header = "no";
|
|
}
|
|
res.stream.connection.close("HTTP_1_1_REQUIRED", res.stream.id - 2);
|
|
return;
|
|
}
|
|
res.setHeader("X-H11Required-Stream-Ok", h11required_header);
|
|
} else if (u.pathname === "/rstonce") {
|
|
if (!didRst && req.httpVersionMajor === 2) {
|
|
didRst = true;
|
|
rstConnection = req.stream.connection;
|
|
req.stream.reset("REFUSED_STREAM");
|
|
return;
|
|
}
|
|
|
|
if (rstConnection === null || rstConnection !== req.stream.connection) {
|
|
if (req.httpVersionMajor != 2) {
|
|
res.setHeader("Connection", "close");
|
|
}
|
|
res.writeHead(400);
|
|
res.end("WRONG CONNECTION, HOMIE!");
|
|
return;
|
|
}
|
|
|
|
// Clear these variables so we can run the test again with --verify
|
|
didRst = false;
|
|
rstConnection = null;
|
|
|
|
if (req.httpVersionMajor != 2) {
|
|
res.setHeader("Connection", "close");
|
|
}
|
|
res.writeHead(200);
|
|
res.end("It's all good.");
|
|
return;
|
|
} else if (u.pathname === "/continuedheaders") {
|
|
var pushRequestHeaders = { "x-pushed-request": "true" };
|
|
var pushResponseHeaders = {
|
|
"content-type": "text/plain",
|
|
"content-length": "2",
|
|
"X-Connection-Http2": "yes",
|
|
};
|
|
var pushHdrTxt =
|
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
var pullHdrTxt = pushHdrTxt.split("").reverse().join("");
|
|
for (let i = 0; i < 265; i++) {
|
|
pushRequestHeaders["X-Push-Test-Header-" + i] = pushHdrTxt;
|
|
res.setHeader("X-Pull-Test-Header-" + i, pullHdrTxt);
|
|
}
|
|
push = res.push({
|
|
hostname: "localhost:" + serverPort,
|
|
port: serverPort,
|
|
path: "/continuedheaders/push",
|
|
method: "GET",
|
|
headers: pushRequestHeaders,
|
|
});
|
|
push.writeHead(200, pushResponseHeaders);
|
|
push.end("ok");
|
|
} else if (u.pathname === "/hugecontinuedheaders") {
|
|
for (let i = 0; i < u.query.size; i++) {
|
|
res.setHeader(
|
|
"X-Test-Header-" + i,
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".repeat(1024)
|
|
);
|
|
}
|
|
res.writeHead(200);
|
|
res.end(content);
|
|
return;
|
|
} else if (u.pathname === "/altsvc1") {
|
|
if (
|
|
req.httpVersionMajor != 2 ||
|
|
req.scheme != "http" ||
|
|
req.headers["alt-used"] != "foo.example.com:" + serverPort
|
|
) {
|
|
res.writeHead(400);
|
|
res.end("WHAT?");
|
|
return;
|
|
}
|
|
// test the alt svc frame for use with altsvc2
|
|
res.altsvc(
|
|
"foo.example.com",
|
|
serverPort,
|
|
"h2",
|
|
3600,
|
|
req.headers["x-redirect-origin"]
|
|
);
|
|
} else if (u.pathname === "/altsvc2") {
|
|
if (
|
|
req.httpVersionMajor != 2 ||
|
|
req.scheme != "http" ||
|
|
req.headers["alt-used"] != "foo.example.com:" + serverPort
|
|
) {
|
|
res.writeHead(400);
|
|
res.end("WHAT?");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// for use with test_altsvc.js
|
|
else if (u.pathname === "/altsvc-test") {
|
|
res.setHeader("Cache-Control", "no-cache");
|
|
res.setHeader("Alt-Svc", "h2=" + req.headers["x-altsvc"]);
|
|
}
|
|
// for use with test_http3.js
|
|
else if (u.pathname === "/http3-test") {
|
|
res.setHeader("Cache-Control", "no-cache");
|
|
res.setHeader("Alt-Svc", "h3-29=" + req.headers["x-altsvc"]);
|
|
}
|
|
// for use with test_http3.js
|
|
else if (u.pathname === "/http3-test2") {
|
|
res.setHeader("Cache-Control", "no-cache");
|
|
res.setHeader(
|
|
"Alt-Svc",
|
|
"h2=foo2.example.com:8000,h3-29=" + req.headers["x-altsvc"]
|
|
);
|
|
} else if (u.pathname === "/http3-test3") {
|
|
res.setHeader("Cache-Control", "no-cache");
|
|
res.setHeader(
|
|
"Alt-Svc",
|
|
"h3-29=" + req.headers["x-altsvc"] + ",h3=" + req.headers["x-altsvc"]
|
|
);
|
|
}
|
|
// for use with test_trr.js
|
|
else if (u.pathname === "/dns-cname") {
|
|
// asking for cname.example.com
|
|
|
|
function emitResponse(response, payload) {
|
|
let pcontent = createCNameContent(payload);
|
|
response.setHeader("Content-Type", "application/dns-message");
|
|
response.setHeader("Content-Length", pcontent.length);
|
|
response.writeHead(200);
|
|
response.write(pcontent);
|
|
response.end("");
|
|
}
|
|
|
|
let payload = Buffer.from("");
|
|
req.on("data", function receiveData(chunk) {
|
|
payload = Buffer.concat([payload, chunk]);
|
|
});
|
|
req.on("end", function finishedData() {
|
|
emitResponse(res, payload);
|
|
});
|
|
return;
|
|
} else if (u.pathname == "/get-doh-req-port-log") {
|
|
let rContent = JSON.stringify(gDoHPortsLog);
|
|
res.setHeader("Content-Type", "text/plain");
|
|
res.setHeader("Content-Length", rContent.length);
|
|
res.writeHead(400);
|
|
res.end(rContent);
|
|
return;
|
|
} else if (u.pathname == "/reset-doh-request-count") {
|
|
gDoHRequestCount = 0;
|
|
res.setHeader("Content-Type", "text/plain");
|
|
res.setHeader("Content-Length", "ok".length);
|
|
res.writeHead(200);
|
|
res.write("ok");
|
|
res.end("");
|
|
return;
|
|
} else if (u.pathname == "/doh") {
|
|
let responseIP = u.query.responseIP;
|
|
if (!responseIP) {
|
|
responseIP = "5.5.5.5";
|
|
}
|
|
|
|
let redirect = u.query.redirect;
|
|
if (redirect) {
|
|
responseIP = redirect;
|
|
if (u.query.dns) {
|
|
res.setHeader(
|
|
"Location",
|
|
"https://localhost:" +
|
|
serverPort +
|
|
"/doh?responseIP=" +
|
|
responseIP +
|
|
"&dns=" +
|
|
u.query.dns
|
|
);
|
|
} else {
|
|
res.setHeader(
|
|
"Location",
|
|
"https://localhost:" + serverPort + "/doh?responseIP=" + responseIP
|
|
);
|
|
}
|
|
res.writeHead(307);
|
|
res.end("");
|
|
return;
|
|
}
|
|
|
|
if (u.query.auth) {
|
|
if (!handleAuth()) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (u.query.noResponse) {
|
|
return;
|
|
}
|
|
|
|
if (u.query.push) {
|
|
// push.example.org has AAAA entry 2018::2018
|
|
let pcontent = dnsPacket.encode({
|
|
id: 0,
|
|
type: "response",
|
|
flags: dnsPacket.RECURSION_DESIRED,
|
|
questions: [{ name: "push.example.org", type: "AAAA", class: "IN" }],
|
|
answers: [
|
|
{
|
|
name: "push.example.org",
|
|
type: "AAAA",
|
|
ttl: 55,
|
|
class: "IN",
|
|
flush: false,
|
|
data: "2018::2018",
|
|
},
|
|
],
|
|
});
|
|
push = res.push({
|
|
hostname: "foo.example.com:" + serverPort,
|
|
port: serverPort,
|
|
path: "/dns-pushed-response?dns=AAAAAAABAAAAAAAABHB1c2gHZXhhbXBsZQNvcmcAABwAAQ",
|
|
method: "GET",
|
|
headers: {
|
|
accept: "application/dns-message",
|
|
},
|
|
});
|
|
push.writeHead(200, {
|
|
"content-type": "application/dns-message",
|
|
pushed: "yes",
|
|
"content-length": pcontent.length,
|
|
"X-Connection-Http2": "yes",
|
|
});
|
|
push.end(pcontent);
|
|
}
|
|
|
|
let payload = Buffer.from("");
|
|
|
|
function emitResponse(response, requestPayload, decodedPacket, delay) {
|
|
let packet = decodedPacket || dnsPacket.decode(requestPayload);
|
|
let answer = createDNSAnswer(
|
|
response,
|
|
packet,
|
|
responseIP,
|
|
requestPayload
|
|
);
|
|
if (!answer) {
|
|
return;
|
|
}
|
|
writeDNSResponse(
|
|
response,
|
|
answer,
|
|
delay || getDelayFromPacket(packet, responseType(packet, responseIP)),
|
|
"application/dns-message"
|
|
);
|
|
}
|
|
|
|
if (u.query.dns) {
|
|
payload = Buffer.from(u.query.dns, "base64");
|
|
emitResponse(res, payload);
|
|
return;
|
|
}
|
|
|
|
req.on("data", function receiveData(chunk) {
|
|
payload = Buffer.concat([payload, chunk]);
|
|
});
|
|
req.on("end", function finishedData() {
|
|
// parload is empty when we send redirect response.
|
|
if (payload.length) {
|
|
let packet = dnsPacket.decode(payload);
|
|
let delay;
|
|
if (u.query.conncycle) {
|
|
let name = packet.questions[0].name;
|
|
if (name.startsWith("newconn")) {
|
|
// If we haven't seen a req for this newconn name before,
|
|
// or if we've seen one for the same name on the same port,
|
|
// synthesize a timeout.
|
|
if (
|
|
!gDoHNewConnLog[name] ||
|
|
gDoHNewConnLog[name] == req.remotePort
|
|
) {
|
|
delay = 1000;
|
|
}
|
|
if (!gDoHNewConnLog[name]) {
|
|
gDoHNewConnLog[name] = req.remotePort;
|
|
}
|
|
}
|
|
gDoHPortsLog.push([packet.questions[0].name, req.remotePort]);
|
|
} else {
|
|
gDoHPortsLog = [];
|
|
gDoHNewConnLog = {};
|
|
}
|
|
emitResponse(res, payload, packet, delay);
|
|
}
|
|
});
|
|
return;
|
|
} else if (u.pathname === "/httpssvc") {
|
|
let payload = Buffer.from("");
|
|
req.on("data", function receiveData(chunk) {
|
|
payload = Buffer.concat([payload, chunk]);
|
|
});
|
|
req.on("end", function finishedData() {
|
|
let packet = dnsPacket.decode(payload);
|
|
let answers = [];
|
|
answers.push({
|
|
name: packet.questions[0].name,
|
|
type: packet.questions[0].type,
|
|
ttl: 55,
|
|
class: "IN",
|
|
flush: false,
|
|
data: {
|
|
priority: 1,
|
|
name: "h3pool",
|
|
values: [
|
|
{ key: "alpn", value: ["h2", "h3"] },
|
|
{ key: "no-default-alpn" },
|
|
{ key: "port", value: 8888 },
|
|
{ key: "ipv4hint", value: "1.2.3.4" },
|
|
{ key: "echconfig", value: "123..." },
|
|
{ key: "ipv6hint", value: "::1" },
|
|
{ key: 30, value: "somelargestring" },
|
|
{ key: "odoh", value: "456..." },
|
|
],
|
|
},
|
|
});
|
|
answers.push({
|
|
name: packet.questions[0].name,
|
|
type: packet.questions[0].type,
|
|
ttl: 55,
|
|
class: "IN",
|
|
flush: false,
|
|
data: {
|
|
priority: 2,
|
|
name: ".",
|
|
values: [
|
|
{ key: "alpn", value: "h2" },
|
|
{ key: "ipv4hint", value: ["1.2.3.4", "5.6.7.8"] },
|
|
{ key: "echconfig", value: "abc..." },
|
|
{ key: "ipv6hint", value: ["::1", "fe80::794f:6d2c:3d5e:7836"] },
|
|
{ key: "odoh", value: "def..." },
|
|
],
|
|
},
|
|
});
|
|
answers.push({
|
|
name: packet.questions[0].name,
|
|
type: packet.questions[0].type,
|
|
ttl: 55,
|
|
class: "IN",
|
|
flush: false,
|
|
data: {
|
|
priority: 3,
|
|
name: "hello",
|
|
values: [],
|
|
},
|
|
});
|
|
let buf = dnsPacket.encode({
|
|
type: "response",
|
|
id: packet.id,
|
|
flags: dnsPacket.RECURSION_DESIRED,
|
|
questions: packet.questions,
|
|
answers,
|
|
});
|
|
|
|
res.setHeader("Content-Type", "application/dns-message");
|
|
res.setHeader("Content-Length", buf.length);
|
|
res.writeHead(200);
|
|
res.write(buf);
|
|
res.end("");
|
|
});
|
|
return;
|
|
} else if (u.pathname === "/httpssvc_as_altsvc") {
|
|
let payload = Buffer.from("");
|
|
req.on("data", function receiveData(chunk) {
|
|
payload = Buffer.concat([payload, chunk]);
|
|
});
|
|
req.on("end", function finishedData() {
|
|
let packet = dnsPacket.decode(payload);
|
|
let answers = [];
|
|
if (packet.questions[0].type == "HTTPS") {
|
|
let priority = 1;
|
|
// Set an invalid priority to test the case when receiving a corrupted
|
|
// response.
|
|
if (packet.questions[0].name === "foo.notexisted.com") {
|
|
priority = 0;
|
|
}
|
|
answers.push({
|
|
name: packet.questions[0].name,
|
|
type: packet.questions[0].type,
|
|
ttl: 55,
|
|
class: "IN",
|
|
flush: false,
|
|
data: {
|
|
priority,
|
|
name: "foo.example.com",
|
|
values: [
|
|
{ key: "alpn", value: "h2" },
|
|
{ key: "port", value: serverPort },
|
|
{ key: 30, value: "somelargestring" },
|
|
],
|
|
},
|
|
});
|
|
} else {
|
|
answers.push({
|
|
name: packet.questions[0].name,
|
|
type: "A",
|
|
ttl: 55,
|
|
flush: false,
|
|
data: "127.0.0.1",
|
|
});
|
|
}
|
|
|
|
let buf = dnsPacket.encode({
|
|
type: "response",
|
|
id: packet.id,
|
|
flags: dnsPacket.RECURSION_DESIRED,
|
|
questions: packet.questions,
|
|
answers,
|
|
});
|
|
|
|
res.setHeader("Content-Type", "application/dns-message");
|
|
res.setHeader("Content-Length", buf.length);
|
|
res.writeHead(200);
|
|
res.write(buf);
|
|
res.end("");
|
|
});
|
|
return;
|
|
} else if (u.pathname === "/httpssvc_use_iphint") {
|
|
let payload = Buffer.from("");
|
|
req.on("data", function receiveData(chunk) {
|
|
payload = Buffer.concat([payload, chunk]);
|
|
});
|
|
req.on("end", function finishedData() {
|
|
let packet = dnsPacket.decode(payload);
|
|
let answers = [];
|
|
answers.push({
|
|
name: packet.questions[0].name,
|
|
type: "HTTPS",
|
|
ttl: 55,
|
|
class: "IN",
|
|
flush: false,
|
|
data: {
|
|
priority: 1,
|
|
name: ".",
|
|
values: [
|
|
{ key: "alpn", value: "h2" },
|
|
{ key: "port", value: serverPort },
|
|
{ key: "ipv4hint", value: "127.0.0.1" },
|
|
],
|
|
},
|
|
});
|
|
|
|
let buf = dnsPacket.encode({
|
|
type: "response",
|
|
id: packet.id,
|
|
flags: dnsPacket.RECURSION_DESIRED,
|
|
questions: packet.questions,
|
|
answers,
|
|
});
|
|
|
|
res.setHeader("Content-Type", "application/dns-message");
|
|
res.setHeader("Content-Length", buf.length);
|
|
res.writeHead(200);
|
|
res.write(buf);
|
|
res.end("");
|
|
});
|
|
return;
|
|
} else if (u.pathname === "/dns-cname-a") {
|
|
let rContent = createCNameARecord();
|
|
res.setHeader("Content-Type", "application/dns-message");
|
|
res.setHeader("Content-Length", rContent.length);
|
|
res.writeHead(200);
|
|
res.write(rContent);
|
|
res.end("");
|
|
return;
|
|
} else if (u.pathname === "/websocket") {
|
|
res.setHeader("Upgrade", "websocket");
|
|
res.setHeader("Connection", "Upgrade");
|
|
var wshash = crypto.createHash("sha1");
|
|
wshash.update(req.headers["sec-websocket-key"]);
|
|
wshash.update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
|
let key = wshash.digest("base64");
|
|
res.setHeader("Sec-WebSocket-Accept", key);
|
|
res.writeHead(101);
|
|
res.end("something....");
|
|
return;
|
|
}
|
|
// for use with test_dns_by_type_resolve.js
|
|
else if (u.pathname === "/txt-dns-push") {
|
|
// _esni_push.example.com has A entry 127.0.0.1
|
|
let rContent = Buffer.from(
|
|
"0000010000010001000000000A5F65736E695F70757368076578616D706C6503636F6D0000010001C00C000100010000003700047F000001",
|
|
"hex"
|
|
);
|
|
|
|
// _esni_push.example.com has TXT entry 2062586B67646D39705932556761584D6762586B676347467A63336476636D513D
|
|
var pcontent = Buffer.from(
|
|
"0000818000010001000000000A5F65736E695F70757368076578616D706C6503636F6D0000100001C00C001000010000003700212062586B67646D39705932556761584D6762586B676347467A63336476636D513D",
|
|
"hex"
|
|
);
|
|
|
|
push = res.push({
|
|
hostname: "foo.example.com:" + serverPort,
|
|
port: serverPort,
|
|
path: "/dns-pushed-response?dns=AAABAAABAAAAAAABCl9lc25pX3B1c2gHZXhhbXBsZQNjb20AABAAAQAAKRAAAAAAAAAIAAgABAABAAA",
|
|
method: "GET",
|
|
headers: {
|
|
accept: "application/dns-message",
|
|
},
|
|
});
|
|
push.writeHead(200, {
|
|
"content-type": "application/dns-message",
|
|
pushed: "yes",
|
|
"content-length": pcontent.length,
|
|
"X-Connection-Http2": "yes",
|
|
});
|
|
push.end(pcontent);
|
|
res.setHeader("Content-Type", "application/dns-message");
|
|
res.setHeader("Content-Length", rContent.length);
|
|
res.writeHead(200);
|
|
res.write(rContent);
|
|
res.end("");
|
|
return;
|
|
} else if (u.pathname === "/.well-known/http-opportunistic") {
|
|
res.setHeader("Cache-Control", "no-cache");
|
|
res.setHeader("Content-Type", "application/json");
|
|
res.writeHead(200, "OK");
|
|
res.end('["http://' + req.headers.host + '"]');
|
|
return;
|
|
} else if (u.pathname === "/stale-while-revalidate-loop-test") {
|
|
res.setHeader(
|
|
"Cache-Control",
|
|
"s-maxage=86400, stale-while-revalidate=86400, immutable"
|
|
);
|
|
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
res.setHeader("Content-Length", "1");
|
|
res.writeHead(200, "OK");
|
|
res.end("1");
|
|
return;
|
|
}
|
|
|
|
// for PushService tests.
|
|
else if (u.pathname === "/pushSubscriptionSuccess/subscribe") {
|
|
res.setHeader(
|
|
"Location",
|
|
"https://localhost:" + serverPort + "/pushSubscriptionSuccesss"
|
|
);
|
|
res.setHeader(
|
|
"Link",
|
|
'</pushEndpointSuccess>; rel="urn:ietf:params:push", ' +
|
|
'</receiptPushEndpointSuccess>; rel="urn:ietf:params:push:receipt"'
|
|
);
|
|
res.writeHead(201, "OK");
|
|
res.end("");
|
|
return;
|
|
} else if (u.pathname === "/pushSubscriptionSuccesss") {
|
|
// do nothing.
|
|
return;
|
|
} else if (u.pathname === "/pushSubscriptionMissingLocation/subscribe") {
|
|
res.setHeader(
|
|
"Link",
|
|
'</pushEndpointMissingLocation>; rel="urn:ietf:params:push", ' +
|
|
'</receiptPushEndpointMissingLocation>; rel="urn:ietf:params:push:receipt"'
|
|
);
|
|
res.writeHead(201, "OK");
|
|
res.end("");
|
|
return;
|
|
} else if (u.pathname === "/pushSubscriptionMissingLink/subscribe") {
|
|
res.setHeader(
|
|
"Location",
|
|
"https://localhost:" + serverPort + "/subscriptionMissingLink"
|
|
);
|
|
res.writeHead(201, "OK");
|
|
res.end("");
|
|
return;
|
|
} else if (u.pathname === "/pushSubscriptionLocationBogus/subscribe") {
|
|
res.setHeader("Location", "1234");
|
|
res.setHeader(
|
|
"Link",
|
|
'</pushEndpointLocationBogus; rel="urn:ietf:params:push", ' +
|
|
'</receiptPushEndpointLocationBogus>; rel="urn:ietf:params:push:receipt"'
|
|
);
|
|
res.writeHead(201, "OK");
|
|
res.end("");
|
|
return;
|
|
} else if (u.pathname === "/pushSubscriptionMissingLink1/subscribe") {
|
|
res.setHeader(
|
|
"Location",
|
|
"https://localhost:" + serverPort + "/subscriptionMissingLink1"
|
|
);
|
|
res.setHeader(
|
|
"Link",
|
|
'</receiptPushEndpointMissingLink1>; rel="urn:ietf:params:push:receipt"'
|
|
);
|
|
res.writeHead(201, "OK");
|
|
res.end("");
|
|
return;
|
|
} else if (u.pathname === "/pushSubscriptionMissingLink2/subscribe") {
|
|
res.setHeader(
|
|
"Location",
|
|
"https://localhost:" + serverPort + "/subscriptionMissingLink2"
|
|
);
|
|
res.setHeader(
|
|
"Link",
|
|
'</pushEndpointMissingLink2>; rel="urn:ietf:params:push"'
|
|
);
|
|
res.writeHead(201, "OK");
|
|
res.end("");
|
|
return;
|
|
} else if (u.pathname === "/subscriptionMissingLink2") {
|
|
// do nothing.
|
|
return;
|
|
} else if (u.pathname === "/pushSubscriptionNot201Code/subscribe") {
|
|
res.setHeader(
|
|
"Location",
|
|
"https://localhost:" + serverPort + "/subscriptionNot2xxCode"
|
|
);
|
|
res.setHeader(
|
|
"Link",
|
|
'</pushEndpointNot201Code>; rel="urn:ietf:params:push", ' +
|
|
'</receiptPushEndpointNot201Code>; rel="urn:ietf:params:push:receipt"'
|
|
);
|
|
res.writeHead(200, "OK");
|
|
res.end("");
|
|
return;
|
|
} else if (u.pathname === "/pushNotifications/subscription1") {
|
|
pushPushServer1 = res.push({
|
|
hostname: "localhost:" + serverPort,
|
|
port: serverPort,
|
|
path: "/pushNotificationsDeliver1",
|
|
method: "GET",
|
|
headers: {
|
|
"Encryption-Key":
|
|
'keyid="notification1"; dh="BO_tgGm-yvYAGLeRe16AvhzaUcpYRiqgsGOlXpt0DRWDRGGdzVLGlEVJMygqAUECarLnxCiAOHTP_znkedrlWoU"',
|
|
Encryption: 'keyid="notification1";salt="uAZaiXpOSfOLJxtOCZ09dA"',
|
|
"Content-Encoding": "aesgcm128",
|
|
},
|
|
});
|
|
pushPushServer1.writeHead(200, {
|
|
subresource: "1",
|
|
});
|
|
|
|
pushPushServer1.end(
|
|
"370aeb3963f12c4f12bf946bd0a7a9ee7d3eaff8f7aec62b530fc25cfa",
|
|
"hex"
|
|
);
|
|
return;
|
|
} else if (u.pathname === "/pushNotifications/subscription2") {
|
|
pushPushServer2 = res.push({
|
|
hostname: "localhost:" + serverPort,
|
|
port: serverPort,
|
|
path: "/pushNotificationsDeliver3",
|
|
method: "GET",
|
|
headers: {
|
|
"Encryption-Key":
|
|
'keyid="notification2"; dh="BKVdQcgfncpNyNWsGrbecX0zq3eHIlHu5XbCGmVcxPnRSbhjrA6GyBIeGdqsUL69j5Z2CvbZd-9z1UBH0akUnGQ"',
|
|
Encryption: 'keyid="notification2";salt="vFn3t3M_k42zHBdpch3VRw"',
|
|
"Content-Encoding": "aesgcm128",
|
|
},
|
|
});
|
|
pushPushServer2.writeHead(200, {
|
|
subresource: "1",
|
|
});
|
|
|
|
pushPushServer2.end(
|
|
"66df5d11daa01e5c802ff97cdf7f39684b5bf7c6418a5cf9b609c6826c04b25e403823607ac514278a7da945",
|
|
"hex"
|
|
);
|
|
return;
|
|
} else if (u.pathname === "/pushNotifications/subscription3") {
|
|
pushPushServer3 = res.push({
|
|
hostname: "localhost:" + serverPort,
|
|
port: serverPort,
|
|
path: "/pushNotificationsDeliver3",
|
|
method: "GET",
|
|
headers: {
|
|
"Encryption-Key":
|
|
'keyid="notification3";dh="BD3xV_ACT8r6hdIYES3BJj1qhz9wyv7MBrG9vM2UCnjPzwE_YFVpkD-SGqE-BR2--0M-Yf31wctwNsO1qjBUeMg"',
|
|
Encryption:
|
|
'keyid="notification3"; salt="DFq188piWU7osPBgqn4Nlg"; rs=24',
|
|
"Content-Encoding": "aesgcm128",
|
|
},
|
|
});
|
|
pushPushServer3.writeHead(200, {
|
|
subresource: "1",
|
|
});
|
|
|
|
pushPushServer3.end(
|
|
"2caaeedd9cf1059b80c58b6c6827da8ff7de864ac8bea6d5775892c27c005209cbf9c4de0c3fbcddb9711d74eaeebd33f7275374cb42dd48c07168bc2cc9df63e045ce2d2a2408c66088a40c",
|
|
"hex"
|
|
);
|
|
return;
|
|
} else if (u.pathname == "/pushNotifications/subscription4") {
|
|
pushPushServer4 = res.push({
|
|
hostname: "localhost:" + serverPort,
|
|
port: serverPort,
|
|
path: "/pushNotificationsDeliver4",
|
|
method: "GET",
|
|
headers: {
|
|
"Crypto-Key":
|
|
'keyid="notification4";dh="BJScXUUTcs7D8jJWI1AOxSgAKkF7e56ay4Lek52TqDlWo1yGd5czaxFWfsuP4j7XNWgGYm60-LKpSUMlptxPFVQ"',
|
|
Encryption: 'keyid="notification4"; salt="sn9p2QqF3V6KBclda8vx7w"',
|
|
"Content-Encoding": "aesgcm",
|
|
},
|
|
});
|
|
pushPushServer4.writeHead(200, {
|
|
subresource: "1",
|
|
});
|
|
|
|
pushPushServer4.end(
|
|
"9eba7ba6192544a39bd9e9b58e702d0748f1776b27f6616cdc55d29ed5a015a6db8f2dd82cd5751a14315546194ff1c18458ab91eb36c9760ccb042670001fd9964557a079553c3591ee131ceb259389cfffab3ab873f873caa6a72e87d262b8684c3260e5940b992234deebf57a9ff3a8775742f3cbcb152d249725a28326717e19cce8506813a155eff5df9bdba9e3ae8801d3cc2b7e7f2f1b6896e63d1fdda6f85df704b1a34db7b2dd63eba11ede154300a318c6f83c41a3d32356a196e36bc905b99195fd91ae4ff3f545c42d17f1fdc1d5bd2bf7516d0765e3a859fffac84f46160b79cedda589f74c25357cf6988cd8ba83867ebd86e4579c9d3b00a712c77fcea3b663007076e21f9819423faa830c2176ff1001c1690f34be26229a191a938517",
|
|
"hex"
|
|
);
|
|
return;
|
|
} else if (
|
|
u.pathname === "/pushNotificationsDeliver1" ||
|
|
u.pathname === "/pushNotificationsDeliver2" ||
|
|
u.pathname === "/pushNotificationsDeliver3"
|
|
) {
|
|
res.writeHead(410, "GONE");
|
|
res.end("");
|
|
return;
|
|
} else if (u.pathname === "/illegalhpacksoft") {
|
|
// This will cause the compressor to compress a header that is not legal,
|
|
// but only affects the stream, not the session.
|
|
illegalheader_conn = req.stream.connection;
|
|
res.setHeader("Content-Type", "text/html");
|
|
res.setHeader("x-softillegalhpack", "true");
|
|
res.writeHead(200);
|
|
res.end(content);
|
|
return;
|
|
} else if (u.pathname === "/illegalhpackhard") {
|
|
// This will cause the compressor to insert an HPACK instruction that will
|
|
// cause a session failure.
|
|
res.setHeader("Content-Type", "text/html");
|
|
res.setHeader("x-hardillegalhpack", "true");
|
|
res.writeHead(200);
|
|
res.end(content);
|
|
return;
|
|
} else if (u.pathname === "/illegalhpack_validate") {
|
|
if (req.stream.connection === illegalheader_conn) {
|
|
res.setHeader("X-Did-Goaway", "no");
|
|
} else {
|
|
res.setHeader("X-Did-Goaway", "yes");
|
|
}
|
|
// Fall through to the default response behavior
|
|
} else if (u.pathname === "/foldedheader") {
|
|
res.setHeader("X-Folded-Header", "this is\n folded");
|
|
// Fall through to the default response behavior
|
|
} else if (u.pathname === "/emptydata") {
|
|
// Overwrite the original transform with our version that will insert an
|
|
// empty DATA frame at the beginning of the stream response, then fall
|
|
// through to the default response behavior.
|
|
Serializer.prototype._transform = newTransform;
|
|
}
|
|
|
|
// for use with test_immutable.js
|
|
else if (u.pathname === "/immutable-test-without-attribute") {
|
|
res.setHeader("Cache-Control", "max-age=100000");
|
|
res.setHeader("Etag", "1");
|
|
if (req.headers["if-none-match"]) {
|
|
res.setHeader("x-conditional", "true");
|
|
}
|
|
// default response from here
|
|
} else if (u.pathname === "/immutable-test-with-attribute") {
|
|
res.setHeader("Cache-Control", "max-age=100000, immutable");
|
|
res.setHeader("Etag", "2");
|
|
if (req.headers["if-none-match"]) {
|
|
res.setHeader("x-conditional", "true");
|
|
}
|
|
// default response from here
|
|
} else if (u.pathname === "/immutable-test-expired-with-Expires-header") {
|
|
res.setHeader("Cache-Control", "immutable");
|
|
res.setHeader("Expires", "Mon, 01 Jan 1990 00:00:00 GMT");
|
|
res.setHeader("Etag", "3");
|
|
|
|
if (req.headers["if-none-match"]) {
|
|
res.setHeader("x-conditional", "true");
|
|
}
|
|
} else if (
|
|
u.pathname === "/immutable-test-expired-with-last-modified-header"
|
|
) {
|
|
res.setHeader("Cache-Control", "public, max-age=3600, immutable");
|
|
res.setHeader("Date", "Mon, 01 Jan 1990 00:00:00 GMT");
|
|
res.setHeader("Last-modified", "Mon, 01 Jan 1990 00:00:00 GMT");
|
|
res.setHeader("Etag", "4");
|
|
|
|
if (req.headers["if-none-match"]) {
|
|
res.setHeader("x-conditional", "true");
|
|
}
|
|
} else if (u.pathname === "/origin-4") {
|
|
let originList = [];
|
|
req.stream.connection.originFrame(originList);
|
|
res.setHeader("x-client-port", req.remotePort);
|
|
} else if (u.pathname === "/origin-6") {
|
|
let originList = [
|
|
"https://alt1.example.com:" + serverPort,
|
|
"https://alt2.example.com:" + serverPort,
|
|
"https://bar.example.com:" + serverPort,
|
|
];
|
|
req.stream.connection.originFrame(originList);
|
|
res.setHeader("x-client-port", req.remotePort);
|
|
} else if (u.pathname === "/origin-11-a") {
|
|
res.setHeader("x-client-port", req.remotePort);
|
|
|
|
const pushb = res.push({
|
|
hostname: "foo.example.com:" + serverPort,
|
|
port: serverPort,
|
|
path: "/origin-11-b",
|
|
method: "GET",
|
|
headers: { "x-pushed-request": "true", "x-foo": "bar" },
|
|
});
|
|
pushb.writeHead(200, {
|
|
pushed: "yes",
|
|
"content-length": 1,
|
|
});
|
|
pushb.end("1");
|
|
|
|
const pushc = res.push({
|
|
hostname: "bar.example.com:" + serverPort,
|
|
port: serverPort,
|
|
path: "/origin-11-c",
|
|
method: "GET",
|
|
headers: { "x-pushed-request": "true", "x-foo": "bar" },
|
|
});
|
|
pushc.writeHead(200, {
|
|
pushed: "yes",
|
|
"content-length": 1,
|
|
});
|
|
pushc.end("1");
|
|
|
|
const pushd = res.push({
|
|
hostname: "madeup.example.com:" + serverPort,
|
|
port: serverPort,
|
|
path: "/origin-11-d",
|
|
method: "GET",
|
|
headers: { "x-pushed-request": "true", "x-foo": "bar" },
|
|
});
|
|
pushd.writeHead(200, {
|
|
pushed: "yes",
|
|
"content-length": 1,
|
|
});
|
|
pushd.end("1");
|
|
|
|
const pushe = res.push({
|
|
hostname: "alt1.example.com:" + serverPort,
|
|
port: serverPort,
|
|
path: "/origin-11-e",
|
|
method: "GET",
|
|
headers: { "x-pushed-request": "true", "x-foo": "bar" },
|
|
});
|
|
pushe.writeHead(200, {
|
|
pushed: "yes",
|
|
"content-length": 1,
|
|
});
|
|
pushe.end("1");
|
|
} else if (u.pathname.substring(0, 8) === "/origin-") {
|
|
// test_origin.js coalescing
|
|
res.setHeader("x-client-port", req.remotePort);
|
|
} else if (u.pathname === "/statusphrase") {
|
|
// Fortunately, the node-http2 API is dumb enough to allow this right on
|
|
// through, so we can easily test rejecting this on gecko's end.
|
|
res.writeHead("200 OK");
|
|
res.end(content);
|
|
return;
|
|
} else if (u.pathname === "/doublepush") {
|
|
push1 = res.push("/doublypushed");
|
|
push1.writeHead(200, {
|
|
"content-type": "text/plain",
|
|
pushed: "yes",
|
|
"content-length": 6,
|
|
"X-Connection-Http2": "yes",
|
|
});
|
|
push1.end("pushed");
|
|
|
|
push2 = res.push("/doublypushed");
|
|
push2.writeHead(200, {
|
|
"content-type": "text/plain",
|
|
pushed: "yes",
|
|
"content-length": 6,
|
|
"X-Connection-Http2": "yes",
|
|
});
|
|
push2.end("pushed");
|
|
} else if (u.pathname === "/doublypushed") {
|
|
content = "not pushed";
|
|
} else if (u.pathname === "/diskcache") {
|
|
content = "this was pulled via h2";
|
|
} else if (u.pathname === "/pushindisk") {
|
|
var pushedContent = "this was pushed via h2";
|
|
push = res.push("/diskcache");
|
|
push.writeHead(200, {
|
|
"content-type": "text/html",
|
|
pushed: "yes",
|
|
"content-length": pushedContent.length,
|
|
"X-Connection-Http2": "yes",
|
|
});
|
|
push.end(pushedContent);
|
|
}
|
|
|
|
// For test_header_Server_Timing.js
|
|
else if (u.pathname === "/server-timing") {
|
|
res.setHeader("Content-Type", "text/plain");
|
|
res.setHeader("Content-Length", "12");
|
|
res.setHeader("Trailer", "Server-Timing");
|
|
res.setHeader(
|
|
"Server-Timing",
|
|
"metric; dur=123.4; desc=description, metric2; dur=456.78; desc=description1"
|
|
);
|
|
res.write("data reached");
|
|
res.addTrailers({
|
|
"Server-Timing":
|
|
"metric3; dur=789.11; desc=description2, metric4; dur=1112.13; desc=description3",
|
|
});
|
|
res.end();
|
|
return;
|
|
} else if (u.pathname === "/redirect_to_http") {
|
|
res.setHeader(
|
|
"Location",
|
|
`http://test.httpsrr.redirect.com:${u.query.port}/redirect_to_http?port=${u.query.port}`
|
|
);
|
|
res.writeHead(307);
|
|
res.end("");
|
|
return;
|
|
} else if (u.pathname === "/103_response") {
|
|
let link_val = req.headers["link-to-set"];
|
|
if (link_val) {
|
|
res.setHeader("link", link_val);
|
|
}
|
|
res.setHeader("something", "something");
|
|
res.writeHead(103);
|
|
|
|
res.setHeader("Content-Type", "text/plain");
|
|
res.setHeader("Content-Length", "12");
|
|
res.writeHead(200);
|
|
res.write("data reached");
|
|
res.end();
|
|
return;
|
|
} else if (u.pathname.startsWith("/invalid_response_header/")) {
|
|
// response headers with invalid characters in the name / value (RFC7540 Sec 10.3)
|
|
let kind = u.pathname.slice("/invalid_response_header/".length);
|
|
if (kind === "name_spaces") {
|
|
res.setHeader("With Spaces", "Hello");
|
|
} else if (kind === "value_line_feed") {
|
|
res.setHeader("invalid-header", "line\nfeed");
|
|
} else if (kind === "value_carriage_return") {
|
|
res.setHeader("invalid-header", "carriage\rreturn");
|
|
} else if (kind === "value_null") {
|
|
res.setHeader("invalid-header", "null\0");
|
|
}
|
|
|
|
res.writeHead(200);
|
|
res.end("");
|
|
return;
|
|
} else if (u.pathname === "/origin_header") {
|
|
let originHeader = req.headers.origin;
|
|
res.setHeader("Content-Length", originHeader.length);
|
|
res.setHeader("Content-Type", "text/plain");
|
|
res.writeHead(200);
|
|
res.write(originHeader);
|
|
res.end();
|
|
return;
|
|
}
|
|
|
|
res.setHeader("Content-Type", "text/html");
|
|
if (req.httpVersionMajor != 2) {
|
|
res.setHeader("Connection", "close");
|
|
}
|
|
res.writeHead(200);
|
|
res.end(content);
|
|
}
|
|
|
|
// Set up the SSL certs for our server - this server has a cert for foo.example.com
|
|
// signed by netwerk/tests/unit/http2-ca.pem
|
|
var options = {
|
|
key: fs.readFileSync(__dirname + "/http2-cert.key"),
|
|
cert: fs.readFileSync(__dirname + "/http2-cert.pem"),
|
|
};
|
|
|
|
if (process.env.HTTP2_LOG !== undefined) {
|
|
var log_module = node_http2_root + "/test/util";
|
|
options.log = require(log_module).createLogger("server");
|
|
}
|
|
|
|
var server = http2.createServer(options, handleRequest);
|
|
|
|
server.on("connection", function (socket) {
|
|
socket.on("error", function () {
|
|
// Ignoring SSL socket errors, since they usually represent a connection that was tore down
|
|
// by the browser because of an untrusted certificate. And this happens at least once, when
|
|
// the first test case if done.
|
|
});
|
|
});
|
|
|
|
server.on("connect", function (req, clientSocket) {
|
|
clientSocket.write(
|
|
"HTTP/1.1 404 Not Found\r\nProxy-agent: Node.js-Proxy\r\n\r\n"
|
|
);
|
|
clientSocket.destroy();
|
|
});
|
|
|
|
function makeid(length) {
|
|
var result = "";
|
|
var characters =
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
var charactersLength = characters.length;
|
|
for (var i = 0; i < length; i++) {
|
|
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
let globalObjects = {};
|
|
var serverPort;
|
|
|
|
const listen = (serv, envport) => {
|
|
if (!serv) {
|
|
return Promise.resolve(0);
|
|
}
|
|
|
|
let portSelection = 0;
|
|
if (envport !== undefined) {
|
|
try {
|
|
portSelection = parseInt(envport, 10);
|
|
} catch (e) {
|
|
portSelection = -1;
|
|
}
|
|
}
|
|
return new Promise(resolve => {
|
|
serv.listen(portSelection, "0.0.0.0", 2000, () => {
|
|
resolve(serv.address().port);
|
|
});
|
|
});
|
|
};
|
|
|
|
const http = require("http");
|
|
let httpServer = http.createServer((req, res) => {
|
|
if (req.method != "POST") {
|
|
let u = url.parse(req.url, true);
|
|
if (u.pathname == "/test") {
|
|
// This path is used to test that the server is working properly
|
|
res.writeHead(200);
|
|
res.end("OK");
|
|
return;
|
|
}
|
|
res.writeHead(405);
|
|
res.end("Unexpected method: " + req.method);
|
|
return;
|
|
}
|
|
|
|
let code = "";
|
|
req.on("data", function receivePostData(chunk) {
|
|
code += chunk;
|
|
});
|
|
req.on("end", function finishPost() {
|
|
let u = url.parse(req.url, true);
|
|
if (u.pathname == "/fork") {
|
|
let id = forkProcess();
|
|
computeAndSendBackResponse(id);
|
|
return;
|
|
}
|
|
|
|
if (u.pathname == "/forkH3Server") {
|
|
forkH3Server(u.query.path, u.query.dbPath)
|
|
.then(result => {
|
|
computeAndSendBackResponse(result);
|
|
})
|
|
.catch(error => {
|
|
computeAndSendBackResponse(error);
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (u.pathname.startsWith("/kill/")) {
|
|
let id = u.pathname.slice(6);
|
|
let forked = globalObjects[id];
|
|
if (!forked) {
|
|
computeAndSendBackResponse(undefined, new Error("could not find id"));
|
|
return;
|
|
}
|
|
|
|
new Promise((resolve, reject) => {
|
|
forked.resolve = resolve;
|
|
forked.reject = reject;
|
|
forked.kill();
|
|
})
|
|
.then(x =>
|
|
computeAndSendBackResponse(
|
|
undefined,
|
|
new Error(`incorrectly resolved ${x}`)
|
|
)
|
|
)
|
|
.catch(e => {
|
|
// We indicate a proper shutdown by resolving with undefined.
|
|
if (e && e.toString().match(/child process exit closing code/)) {
|
|
e = undefined;
|
|
}
|
|
computeAndSendBackResponse(undefined, e);
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (u.pathname.startsWith("/execute/")) {
|
|
let id = u.pathname.slice(9);
|
|
let forked = globalObjects[id];
|
|
if (!forked) {
|
|
computeAndSendBackResponse(undefined, new Error("could not find id"));
|
|
return;
|
|
}
|
|
|
|
new Promise((resolve, reject) => {
|
|
forked.resolve = resolve;
|
|
forked.reject = reject;
|
|
forked.send({ code });
|
|
})
|
|
.then(x => sendBackResponse(x))
|
|
.catch(e => computeAndSendBackResponse(undefined, e));
|
|
}
|
|
|
|
function computeAndSendBackResponse(evalResult, e) {
|
|
let output = { result: evalResult, error: "", errorStack: "" };
|
|
if (e) {
|
|
output.error = e.toString();
|
|
output.errorStack = e.stack;
|
|
}
|
|
sendBackResponse(output);
|
|
}
|
|
|
|
function sendBackResponse(output) {
|
|
output = JSON.stringify(output);
|
|
|
|
res.setHeader("Content-Length", output.length);
|
|
res.setHeader("Content-Type", "application/json");
|
|
res.writeHead(200);
|
|
res.write(output);
|
|
res.end("");
|
|
}
|
|
});
|
|
});
|
|
|
|
function forkH3Server(serverPath, dbPath) {
|
|
const args = [dbPath];
|
|
let process = spawn(serverPath, args);
|
|
let id = forkProcessInternal(process);
|
|
// Return a promise that resolves when we receive data from stdout
|
|
return new Promise((resolve, _) => {
|
|
process.stdout.on("data", data => {
|
|
console.log(data.toString());
|
|
resolve({ id, output: data.toString().trim() });
|
|
});
|
|
});
|
|
}
|
|
|
|
function forkProcess() {
|
|
let scriptPath = path.resolve(__dirname, "moz-http2-child.js");
|
|
let forked = fork(scriptPath);
|
|
return forkProcessInternal(forked);
|
|
}
|
|
|
|
function forkProcessInternal(forked) {
|
|
let id = makeid(6);
|
|
forked.errors = "";
|
|
globalObjects[id] = forked;
|
|
forked.on("message", msg => {
|
|
if (forked.resolve) {
|
|
forked.resolve(msg);
|
|
forked.resolve = null;
|
|
} else {
|
|
console.log(
|
|
`forked process without handler sent: ${JSON.stringify(msg)}`
|
|
);
|
|
forked.errors += `forked process without handler sent: ${JSON.stringify(
|
|
msg
|
|
)}\n`;
|
|
}
|
|
});
|
|
|
|
let exitFunction = (code, signal) => {
|
|
if (globalObjects[id]) {
|
|
delete globalObjects[id];
|
|
} else {
|
|
// already called
|
|
return;
|
|
}
|
|
|
|
if (!forked.reject) {
|
|
console.log(
|
|
`child process ${id} closing code: ${code} signal: ${signal}`
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (forked.errors != "") {
|
|
forked.reject(forked.errors);
|
|
forked.errors = "";
|
|
forked.reject = null;
|
|
return;
|
|
}
|
|
|
|
forked.reject(`child process exit closing code: ${code} signal: ${signal}`);
|
|
forked.reject = null;
|
|
};
|
|
|
|
forked.on("error", exitFunction);
|
|
forked.on("close", exitFunction);
|
|
forked.on("exit", exitFunction);
|
|
|
|
return id;
|
|
}
|
|
|
|
Promise.all([
|
|
listen(server, process.env.MOZHTTP2_PORT).then(port => (serverPort = port)),
|
|
listen(httpServer, process.env.MOZNODE_EXEC_PORT),
|
|
]).then(([sPort, nodeExecPort]) => {
|
|
console.log(`HTTP2 server listening on ports ${sPort},${nodeExecPort}`);
|
|
});
|