http2_server.js (4502B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 /* globals require, __dirname, global, Buffer, process */ 8 9 const fs = require("fs"); 10 const options = { 11 key: fs.readFileSync(__dirname + "/mochitest-cert.key.pem"), 12 cert: fs.readFileSync(__dirname + "/mochitest-cert.pem"), 13 settings: { 14 enableConnectProtocol: true, 15 }, 16 }; 17 const http2 = require("http2"); 18 const http = require("http"); 19 const url = require("url"); 20 const path = require("path"); 21 22 // This is the path of node-ws when running mochitest locally. 23 let node_ws_root = path.join(__dirname, "../../xpcshell/node-ws"); 24 if (!fs.existsSync(node_ws_root)) { 25 // This path is for running mochitest on try. 26 node_ws_root = path.join(__dirname, "./node_ws"); 27 } 28 29 const WebSocket = require(`${node_ws_root}/lib/websocket`); 30 31 let listeningPort = parseInt(process.argv[3].split("=")[1]); 32 let log = function () {}; 33 34 function handle_h2_non_connect(stream, headers) { 35 const session = stream.session; 36 const uri = new URL( 37 `${headers[":scheme"]}://${headers[":authority"]}${headers[":path"]}` 38 ); 39 const url = uri.toString(); 40 41 log("REQUEST:", url); 42 log("REQUEST HEADER:", JSON.stringify(headers)); 43 44 stream.on("close", () => { 45 log("REQUEST STREAM CLOSED:", stream.rstCode); 46 }); 47 stream.on("error", error => { 48 log("RESPONSE STREAM ERROR", error, url, "on session:", session.__id); 49 }); 50 51 let newHeaders = {}; 52 for (let key in headers) { 53 if (!key.startsWith(":")) { 54 newHeaders[key] = headers[key]; 55 } 56 } 57 58 const options = { 59 protocol: "http:", 60 hostname: "127.0.0.1", 61 port: 8888, 62 path: headers[":path"], 63 method: headers[":method"], 64 headers: newHeaders, 65 }; 66 67 log("OPTION:", JSON.stringify(options)); 68 const request = http.request(options); 69 70 stream.pipe(request); 71 72 request.on("response", response => { 73 const headers = Object.fromEntries( 74 Object.entries(response.headers).filter( 75 ([key]) => 76 !["connection", "transfer-encoding", "keep-alive"].includes(key) 77 ) 78 ); 79 headers[":status"] = response.statusCode; 80 log("RESPONSE BEGIN", url, headers, "on session:", session.__id); 81 82 try { 83 stream.respond(headers); 84 85 response.on("data", data => { 86 log("RESPONSE DATA", data.length, url); 87 stream.write(data); 88 }); 89 response.on("error", error => { 90 log("RESPONSE ERROR", error, url, "on session:", session.__id); 91 stream.close(http2.constants.NGHTTP2_REFUSED_STREAM); 92 }); 93 response.on("end", () => { 94 log("RESPONSE END", url, "on session:", session.__id); 95 stream.end(); 96 }); 97 } catch (exception) { 98 log("RESPONSE EXCEPTION", exception, url, "on session:", session.__id); 99 stream.close(); 100 } 101 }); 102 request.on("error", error => { 103 console.error("REQUEST ERROR", error, url, "on session:", session.__id); 104 try { 105 stream.respond({ 106 ":status": 502, 107 "content-type": "application/proxy-explanation+json", 108 }); 109 stream.end( 110 JSON.stringify({ 111 title: "request error", 112 description: error.toString(), 113 }) 114 ); 115 } catch (exception) { 116 stream.close(); 117 } 118 }); 119 } 120 121 let server = http2.createSecureServer(options); 122 123 let session_count = 0; 124 let session_id = 0; 125 126 server.on("session", session => { 127 session.__id = ++session_id; 128 session.__tunnel_count = 0; 129 130 ++session_count; 131 if (session_count === 1) { 132 log(`\n\n>>> FIRST SESSION OPENING\n`); 133 } 134 log(`*** NEW SESSION`, session.__id, "( sessions:", session_count, ")"); 135 136 session.on("error", error => { 137 console.error("SESSION ERROR", session.__id, error); 138 }); 139 session.on("close", () => { 140 --session_count; 141 log(`*** CLOSED SESSION`, session.__id, "( sessions:", session_count, ")"); 142 if (!session_count) { 143 log(`\n\n<<< LAST SESSION CLOSED\n`); 144 } 145 }); 146 }); 147 148 server.on("stream", (stream, headers) => { 149 if (headers[":method"] === "CONNECT") { 150 stream.respond(); 151 152 const ws = new WebSocket(null); 153 stream.setNoDelay = () => {}; 154 ws.setSocket(stream, Buffer.from(""), 100 * 1024 * 1024); 155 156 ws.on("message", data => { 157 ws.send(data); 158 }); 159 } else { 160 handle_h2_non_connect(stream, headers); 161 } 162 }); 163 164 server.listen(listeningPort); 165 166 console.log(`Http2 server listening on ports ${server.address().port}`);