166 lines
4.4 KiB
JavaScript
166 lines
4.4 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/. */
|
|
|
|
"use strict";
|
|
|
|
/* globals require, __dirname, global, Buffer, process */
|
|
|
|
const fs = require("fs");
|
|
const options = {
|
|
key: fs.readFileSync(__dirname + "/mochitest-cert.key.pem"),
|
|
cert: fs.readFileSync(__dirname + "/mochitest-cert.pem"),
|
|
settings: {
|
|
enableConnectProtocol: true,
|
|
},
|
|
};
|
|
const http2 = require("http2");
|
|
const http = require("http");
|
|
const url = require("url");
|
|
const path = require("path");
|
|
|
|
// This is the path of node-ws when running mochitest locally.
|
|
let node_ws_root = path.join(__dirname, "../../xpcshell/node-ws");
|
|
if (!fs.existsSync(node_ws_root)) {
|
|
// This path is for running mochitest on try.
|
|
node_ws_root = path.join(__dirname, "./node_ws");
|
|
}
|
|
|
|
const WebSocket = require(`${node_ws_root}/lib/websocket`);
|
|
|
|
let listeningPort = parseInt(process.argv[3].split("=")[1]);
|
|
let log = function () {};
|
|
|
|
function handle_h2_non_connect(stream, headers) {
|
|
const session = stream.session;
|
|
const uri = new URL(
|
|
`${headers[":scheme"]}://${headers[":authority"]}${headers[":path"]}`
|
|
);
|
|
const url = uri.toString();
|
|
|
|
log("REQUEST:", url);
|
|
log("REQUEST HEADER:", JSON.stringify(headers));
|
|
|
|
stream.on("close", () => {
|
|
log("REQUEST STREAM CLOSED:", stream.rstCode);
|
|
});
|
|
stream.on("error", error => {
|
|
log("RESPONSE STREAM ERROR", error, url, "on session:", session.__id);
|
|
});
|
|
|
|
let newHeaders = {};
|
|
for (let key in headers) {
|
|
if (!key.startsWith(":")) {
|
|
newHeaders[key] = headers[key];
|
|
}
|
|
}
|
|
|
|
const options = {
|
|
protocol: "http:",
|
|
hostname: "127.0.0.1",
|
|
port: 8888,
|
|
path: headers[":path"],
|
|
method: headers[":method"],
|
|
headers: newHeaders,
|
|
};
|
|
|
|
log("OPTION:", JSON.stringify(options));
|
|
const request = http.request(options);
|
|
|
|
stream.pipe(request);
|
|
|
|
request.on("response", response => {
|
|
const headers = Object.fromEntries(
|
|
Object.entries(response.headers).filter(
|
|
([key]) =>
|
|
!["connection", "transfer-encoding", "keep-alive"].includes(key)
|
|
)
|
|
);
|
|
headers[":status"] = response.statusCode;
|
|
log("RESPONSE BEGIN", url, headers, "on session:", session.__id);
|
|
|
|
try {
|
|
stream.respond(headers);
|
|
|
|
response.on("data", data => {
|
|
log("RESPONSE DATA", data.length, url);
|
|
stream.write(data);
|
|
});
|
|
response.on("error", error => {
|
|
log("RESPONSE ERROR", error, url, "on session:", session.__id);
|
|
stream.close(http2.constants.NGHTTP2_REFUSED_STREAM);
|
|
});
|
|
response.on("end", () => {
|
|
log("RESPONSE END", url, "on session:", session.__id);
|
|
stream.end();
|
|
});
|
|
} catch (exception) {
|
|
log("RESPONSE EXCEPTION", exception, url, "on session:", session.__id);
|
|
stream.close();
|
|
}
|
|
});
|
|
request.on("error", error => {
|
|
console.error("REQUEST ERROR", error, url, "on session:", session.__id);
|
|
try {
|
|
stream.respond({
|
|
":status": 502,
|
|
"content-type": "application/proxy-explanation+json",
|
|
});
|
|
stream.end(
|
|
JSON.stringify({
|
|
title: "request error",
|
|
description: error.toString(),
|
|
})
|
|
);
|
|
} catch (exception) {
|
|
stream.close();
|
|
}
|
|
});
|
|
}
|
|
|
|
let server = http2.createSecureServer(options);
|
|
|
|
let session_count = 0;
|
|
let session_id = 0;
|
|
|
|
server.on("session", session => {
|
|
session.__id = ++session_id;
|
|
session.__tunnel_count = 0;
|
|
|
|
++session_count;
|
|
if (session_count === 1) {
|
|
log(`\n\n>>> FIRST SESSION OPENING\n`);
|
|
}
|
|
log(`*** NEW SESSION`, session.__id, "( sessions:", session_count, ")");
|
|
|
|
session.on("error", error => {
|
|
console.error("SESSION ERROR", session.__id, error);
|
|
});
|
|
session.on("close", () => {
|
|
--session_count;
|
|
log(`*** CLOSED SESSION`, session.__id, "( sessions:", session_count, ")");
|
|
if (!session_count) {
|
|
log(`\n\n<<< LAST SESSION CLOSED\n`);
|
|
}
|
|
});
|
|
});
|
|
|
|
server.on("stream", (stream, headers) => {
|
|
if (headers[":method"] === "CONNECT") {
|
|
stream.respond();
|
|
|
|
const ws = new WebSocket(null);
|
|
stream.setNoDelay = () => {};
|
|
ws.setSocket(stream, Buffer.from(""), 100 * 1024 * 1024);
|
|
|
|
ws.on("message", data => {
|
|
ws.send(data);
|
|
});
|
|
} else {
|
|
handle_h2_non_connect(stream, headers);
|
|
}
|
|
});
|
|
|
|
server.listen(listeningPort);
|
|
|
|
console.log(`Http2 server listening on ports ${server.address().port}`);
|