diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a2a0a67 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +*.js linguist-language=Typescript +*.css linguist-language=Typescript +*.html linguist-language=Typescript +*.jsx linguist-language=Typescript +*.ts linguist-language=Typescript \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f5164ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +*.pem \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..a35f439 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1 @@ +{ "tabWidth": 4, "arrowParens": "always" } diff --git a/LICENSE b/LICENSE index 3d983a8..51decc2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,10 +1,13 @@ -Copyright 2014 Brian White. All rights reserved. + +The MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to -deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -sell copies of the Software, and to permit persons to whom the Software is +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in @@ -14,6 +17,26 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. \ No newline at end of file +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +特此授予获得副本的任何人免费的许可 +本软件和相关文档文件(以下简称“软件”) +不受限制地处理软件,包括但不限于 +使用,复制,修改,合并,发布,分发,再许可和/或授权的权利 +出售本软件的副本,并允许本软件所涉及的人 +具备以下条件: + +上述版权声明和此许可声明应包含在 +软件的所有副本或实质性部分。 + +本软件按“原样”提供,没有任何形式的明示或明示担保。 +暗示(包括但不限于对适销性的保证), +适用于特定目的和非侵权。在任何情况下都不会 +作者或版权持有人对任何索赔,损害或其他责任 +无论是在合同,侵权或其他情况下的责任 +来自,与软件或使用或其他交易有关的,与之相关的 +在软件中。 + diff --git a/README.md b/README.md index 8d1ac54..7c3f237 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,126 @@ -Description -=========== +# Description -A module for serving http and https connections over the same port. +Serve http and https and spdy and http2 connections over the same port with node.js -Forked from the original [`httpolyglot`](https://github.com/mscdex/httpolyglot) to fix various issues required for [HTTP Toolkit](https://httptoolkit.tech), including: +# Requirements -* Fixing `tlsClientError`: https://github.com/mscdex/httpolyglot/pull/11. -* Exposing the lost bytes from https://github.com/mscdex/httpolyglot/issues/13 on the socket, as `__httpPeekedData`. -* Dropping support for old versions of Node (and thereby simplifying the code somewhat) +- [node.js](http://nodejs.org/) -- v12.0.0 or newer -Requirements -============ +# Install -* [node.js](http://nodejs.org/) -- v8.0.0 or newer +```shell +yarn add spdy @masx200/http-https-spdy-http2-polyglot +``` + +# Connection protocol judgment + +Determine if the connection is over tls. + +```js +const istls = "encrypted" in req.socket; +``` + +Determine if the connection is `http/2`. + +```js +const ishttp2 = "h2" === req.socket.alpnProtocol; +``` +# Examples -Install -============ +- http2 server push - npm install @httptoolkit/httpolyglot +https://github.com/masx200/http-https-spdy-http2-polyglot/blob/master/test/push.js +- Websocket server -Examples -======== +https://github.com/masx200/http-https-spdy-http2-polyglot/blob/master/test/websocket.js -* Simple usage: +- Simple Determine the connection protocol ```javascript -const httpolyglot = require('@httptoolkit/httpolyglot'); -const fs = require('fs'); - -httpolyglot.createServer({ - key: fs.readFileSync('server.key'), - cert: fs.readFileSync('server.crt') -}, function(req, res) { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end((req.socket.encrypted ? 'HTTPS' : 'HTTP') + ' Connection!'); -}).listen(9000, 'localhost', function() { - console.log('httpolyglot server listening on port 9000'); - // visit http://localhost:9000 and https://localhost:9000 in your browser ... +const httpolyglot = require("@masx200/http-https-spdy-http2-polyglot"); +const fs = require("fs"); +const port = 9000; +const server = httpolyglot.createServer( + { + key: fs.readFileSync("server.key.pem"), + cert: fs.readFileSync("server.crt.pem"), + }, + function (req, res) { + res.writeHead(200, { "Content-Type": "text/html" }); + res.end( + ("encrypted" in req.socket ? "HTTPS" : "HTTP") + " Connection!" + ); + } +); +server.listen(port, "localhost", function () { + console.log("httpolyglot server listening on port " + port); }); ``` -* Simple redirect of all http connections to https: +- redirect all http connections to https: -```javascript -const httpolyglot = require('@httptoolkit/httpolyglot'); -const fs = require('fs'); - -httpolyglot.createServer({ - key: fs.readFileSync('server.key'), - cert: fs.readFileSync('server.crt') -}, function(req, res) { - if (!req.socket.encrypted) { - res.writeHead(301, { 'Location': 'https://localhost:9000' }); - return res.end(); - } - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('Welcome, HTTPS user!'); -}).listen(9000, 'localhost', function() { - console.log('httpolyglot server listening on port 9000'); - // visit http://localhost:9000 and https://localhost:9000 in your browser ... -}); +https://github.com/masx200/http-https-spdy-http2-polyglot/blob/master/test/redirect.js + +- create a "404 not found" server + +https://github.com/masx200/http-https-spdy-http2-polyglot/blob/master/test/notfound.js + +# API + +## Exports + +https://github.com/masx200/http-https-spdy-http2-polyglot/blob/master/lib/index.d.ts + +- **createServer** - Creates and returns a new Server instance. + +```ts +declare function createServer( + config: ServerOptions, + requestListener?: RequestListener, + upgradeListener?: UpgradeListener +): net.Server; ``` +The `requestListener` is a function which is automatically added to the 'request' event + +The `upgradeListener` is a function which is automatically added to the 'upgrade' event + +If no "requestListener" or "upgradeListener" is provided, the default "404 not found" listener will be used instead. + +# How it Works -API -=== +https://github.com/lvgithub/blog/blob/master/http_and_https_over_same_port/README.MD -Exports -------- +TLS and HTTP connections are easy to distinguish based on the first byte sent by clients trying to connect. See this comment for more information. -* **Server** - A class similar to https.Server (except instances have `setTimeout()` from http.Server). +https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155 -* **createServer**(< _object_ >tlsConfig[, < _function_ >requestListener]) - _Server_ - Creates and returns a new Server instance. +https://github.com/httptoolkit/httpolyglot/blob/master/lib/index.js -How it Works -============ +# test + +```powershell +yarn install +``` + +```powershell +./generate-ssl-cert.ps1 +``` -TLS and HTTP connections are easy to distinguish based on the first byte sent by clients trying to connect. See [this comment](https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155) for more information. +```powershell +yarn build +``` + +```powershell +yarn serve +``` + +```powershell +yarn test +``` + +```powershell +yarn open +``` diff --git a/api-extractor.json b/api-extractor.json new file mode 100644 index 0000000..ce00a70 --- /dev/null +++ b/api-extractor.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "mainEntryPointFilePath": "/lib/index.d.ts", + "bundledPackages": [], + "compiler": {}, + "apiReport": { "enabled": false }, + "docModel": { "enabled": false }, + "dtsRollup": { + "enabled": true, + "untrimmedFilePath": "/dist/index.d.ts" + }, + "tsdocMetadata": { "enabled": false }, + "messages": { + "compilerMessageReporting": { "default": { "logLevel": "warning" } }, + "extractorMessageReporting": { + "ae-missing-release-tag": { "logLevel": "none" }, + "default": { "logLevel": "warning" } + }, + "tsdocMessageReporting": { "default": { "logLevel": "warning" } } + } +} diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..74d6844 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,62 @@ +/// +import http from "http"; +import https from "https"; +import net from "net"; +import spdy from "spdy"; +import stream from "stream"; +import tls from "tls"; + +export declare function createServer( + config: ServerOptions, + requestListener?: RequestListener, + upgradeListener?: UpgradeListener +): net.Server; + +export declare interface PushOptions { + status?: number; + method?: string; + request?: http.OutgoingHttpHeaders; + response?: http.OutgoingHttpHeaders; +} + +export declare type RequestListener = ( + req: ServerRequest, + res: ServerResponse +) => void; + +export declare const requestNotFound: ( + req: ServerRequest, + res: ServerResponse +) => void; + +export declare type ServerOptions = spdy.ServerOptions & { + allowHalfOpen?: boolean | undefined; + pauseOnConnect?: boolean | undefined; +} & http.ServerOptions & + tls.TlsOptions & + https.ServerOptions; + +export declare interface ServerRequest extends http.IncomingMessage { + socket: Socket; +} + +export declare interface ServerResponse extends http.ServerResponse { + socket: Socket; + push?: (pathname: string, options?: PushOptions) => stream.Writable; +} + +export declare type Socket = Partial & net.Socket; + +export declare type UpgradeListener = ( + req: ServerRequest, + socket: Socket, + head: Buffer +) => void; + +export declare const upgradeNotFound: ( + req: ServerRequest, + socket: Socket, + head: Buffer +) => void; + +export {}; diff --git a/generate-ssl-cert.ps1 b/generate-ssl-cert.ps1 new file mode 100644 index 0000000..0cb5b7d --- /dev/null +++ b/generate-ssl-cert.ps1 @@ -0,0 +1 @@ +openssl req -x509 -days 365 -newkey rsa:2048 -nodes -sha256 -subj "/C=CN/ST=Shanghai/L=Shanghai/O=boyusc/OU=boyusc/CN=localhost" -keyout "server.key.pem" -out "server.crt.pem" \ No newline at end of file diff --git a/lib/declaration.d.ts b/lib/declaration.d.ts new file mode 100644 index 0000000..844cace --- /dev/null +++ b/lib/declaration.d.ts @@ -0,0 +1,46 @@ +/// +import http from "http"; +import net from "net"; +import spdy from "spdy"; +import tls from "tls"; +import stream from "stream"; +import https from "https"; +export interface ServerRequest extends http.IncomingMessage { + socket: Socket; +} +export interface PushOptions { + status?: number; + method?: string; + request?: http.OutgoingHttpHeaders; + response?: http.OutgoingHttpHeaders; +} +export interface ServerResponse extends http.ServerResponse { + socket: Socket; + push?: (pathname: string, options?: PushOptions) => stream.Writable; +} +export declare type Socket = Partial & net.Socket; +export declare type RequestListener = ( + req: ServerRequest, + res: ServerResponse +) => void; +export declare type UpgradeListener = ( + req: ServerRequest, + socket: Socket, + head: Buffer +) => void; +export declare type ServerOptions = spdy.ServerOptions & { + allowHalfOpen?: boolean | undefined; + pauseOnConnect?: boolean | undefined; +} & http.ServerOptions & + tls.TlsOptions & + https.ServerOptions; +export declare const requestNotFound: ( + req: ServerRequest, + res: ServerResponse +) => void; +export declare const upgradeNotFound: ( + req: ServerRequest, + socket: Socket, + head: Buffer +) => void; +//# sourceMappingURL=declaration.d.ts.map diff --git a/lib/declaration.d.ts.map b/lib/declaration.d.ts.map new file mode 100644 index 0000000..ea0d239 --- /dev/null +++ b/lib/declaration.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"declaration.d.ts","sourceRoot":"","sources":["declaration.ts"],"names":[],"mappings":";AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,eAAe;IACvD,MAAM,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,WAAW;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAC;IACnC,QAAQ,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAC;CACvC;AACD,MAAM,WAAW,cAAe,SAAQ,IAAI,CAAC,cAAc;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,KAAK,MAAM,CAAC,QAAQ,CAAC;CACvE;AACD,oBAAY,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC;AACzD,oBAAY,eAAe,GAAG,CAAC,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,cAAc,KAAK,IAAI,CAAC;AAChF,oBAAY,eAAe,GAAG,CAC1B,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,KACX,IAAI,CAAC;AACV,oBAAY,aAAa,GAAG,IAAI,CAAC,aAAa,GAAG;IAC7C,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC,GAAG,IAAI,CAAC,aAAa,GAClB,GAAG,CAAC,UAAU,GACd,KAAK,CAAC,aAAa,CAAC;AACxB,eAAO,MAAM,eAAe,mDAQ3B,CAAC;AACF,eAAO,MAAM,eAAe,4DAO3B,CAAC"} \ No newline at end of file diff --git a/lib/declaration.js b/lib/declaration.js new file mode 100644 index 0000000..5d8511a --- /dev/null +++ b/lib/declaration.js @@ -0,0 +1,10 @@ +export const requestNotFound = function (req, res) { + res.statusCode = 404; + res.setHeader("content-type", "text/html"); + res.write("404"); + res.end(); +}; +export const upgradeNotFound = function (req, socket, head) { + socket.write(`HTTP/1.1 404 Not Found\r\nConnection: keep-alive\r\n\r\n`); + socket.destroy(); +}; diff --git a/lib/declaration.ts b/lib/declaration.ts new file mode 100644 index 0000000..e84e887 --- /dev/null +++ b/lib/declaration.ts @@ -0,0 +1,49 @@ +import http from "http"; +import net from "net"; +import spdy from "spdy"; +import tls from "tls"; +import stream from "stream"; +import https from "https"; +export interface ServerRequest extends http.IncomingMessage { + socket: Socket; +} +export interface PushOptions { + status?: number; + method?: string; + request?: http.OutgoingHttpHeaders; + response?: http.OutgoingHttpHeaders; +} +export interface ServerResponse extends http.ServerResponse { + socket: Socket; + push?: (pathname: string, options?: PushOptions) => stream.Writable; +} +export type Socket = Partial & net.Socket; +export type RequestListener = (req: ServerRequest, res: ServerResponse) => void; +export type UpgradeListener = ( + req: ServerRequest, + socket: Socket, + head: Buffer +) => void; +export type ServerOptions = spdy.ServerOptions & { + allowHalfOpen?: boolean | undefined; + pauseOnConnect?: boolean | undefined; +} & http.ServerOptions & + tls.TlsOptions & + https.ServerOptions; +export const requestNotFound = function ( + req: ServerRequest, + res: ServerResponse +) { + res.statusCode = 404; + res.setHeader("content-type", "text/html"); + res.write("404"); + res.end(); +}; +export const upgradeNotFound = function ( + req: ServerRequest, + socket: Socket, + head: Buffer +) { + socket.write(`HTTP/1.1 404 Not Found\r\nConnection: keep-alive\r\n\r\n`); + socket.destroy(); +}; diff --git a/lib/index.d.ts b/lib/index.d.ts new file mode 100644 index 0000000..30de2c4 --- /dev/null +++ b/lib/index.d.ts @@ -0,0 +1,15 @@ +/// +import net from "net"; +import { + RequestListener, + ServerOptions, + UpgradeListener, +} from "./declaration.js"; +export * from "./declaration.js"; +export { createServer }; +declare function createServer( + config: ServerOptions, + requestListener?: RequestListener, + upgradeListener?: UpgradeListener +): net.Server; +//# sourceMappingURL=index.d.ts.map diff --git a/lib/index.d.ts.map b/lib/index.d.ts.map new file mode 100644 index 0000000..2148be6 --- /dev/null +++ b/lib/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AAEA,OAAO,GAAG,MAAM,KAAK,CAAC;AAGtB,OAAO,EACH,eAAe,EAEf,aAAa,EACb,eAAe,EAElB,MAAM,kBAAkB,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB,iBAAS,YAAY,CACjB,MAAM,EAAE,aAAa,EACrB,eAAe,GAAE,eAAiC,EAClD,eAAe,GAAE,eAAiC,GACnD,GAAG,CAAC,MAAM,CAoFZ"} \ No newline at end of file diff --git a/lib/index.js b/lib/index.js index f794c4b..4d2ee00 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,87 +1,69 @@ -const http = require('http'); -const https = require('https'); -const inherits = require('util').inherits; -const httpSocketHandler = http._connectionListener; - -function Server(tlsconfig, requestListener) { - if (!(this instanceof Server)) return new Server(tlsconfig, requestListener); - - if (typeof tlsconfig === 'function') { - requestListener = tlsconfig; - tlsconfig = undefined; - } - - if (typeof tlsconfig === 'object') { - this.removeAllListeners('connection'); - - https.Server.call(this, tlsconfig, requestListener); - - // capture https socket handler, it's not exported like http's socket - // handler - const connev = this._events.connection; - if (typeof connev === 'function') { - this._tlsHandler = connev; - } else { - this._tlsHandler = connev[connev.length - 1]; +import assert from "assert"; +import http from "http"; +import net from "net"; +import spdy from "spdy"; +import { requestNotFound, upgradeNotFound } from "./declaration.js"; +export * from "./declaration.js"; +export { createServer }; +function createServer( + config, + requestListener = requestNotFound, + upgradeListener = upgradeNotFound +) { + if (!(config && typeof config === "object")) { + throw new Error("options are required!"); } - this.removeListener('connection', this._tlsHandler); - - this._connListener = connectionListener; - this.on('connection', connectionListener); - - // copy from http.Server - this.timeout = 2 * 60 * 1000; - this.allowHalfOpen = true; - this.httpAllowHalfOpen = false; - } else { - http.Server.call(this, requestListener); - } -} -inherits(Server, https.Server); - -Server.prototype.setTimeout = function (msecs, callback) { - this.timeout = msecs; - if (callback) this.on('timeout', callback); -}; - -Server.prototype.__httpSocketHandler = httpSocketHandler; - -function onError(err) {} - -const connectionListener = function (socket) { - const data = socket.read(1); - - if (data === null) { - socket.removeListener('error', onError); - socket.on('error', onError); - - socket.once('readable', () => { - this._connListener(socket); + requestListener = requestListener || requestNotFound; + upgradeListener = upgradeListener || upgradeNotFound; + const servernet = net.createServer(config); + const serverhttp = http.createServer(config); + const serverspdy = spdy.createServer(config); + servernet.addListener("error", () => {}); + serverhttp.addListener("ClientError", (err, socket) => { + socket.destroy(); + }); + serverhttp.addListener("error", () => {}); + serverspdy.addListener("tlsClientError", (err, socket) => { + socket.destroy(); + }); + serverspdy.addListener("error", () => {}); + serverspdy.prependListener("secureConnection", (socket) => { + if (!socket.listeners("error").length) { + socket.on("error", () => {}); + } }); - } else { - socket.removeListener('error', onError); - - const firstByte = data[0]; - socket.unshift(data); - if (firstByte < 32 || firstByte >= 127) { - // tls/ssl - // TLS sockets don't allow half open - socket.allowHalfOpen = false; - this._tlsHandler(socket); - } else { - // The above unshift isn't always sufficient to invisibly replace the - // read data. The rawPacket property on errors in the clientError event - // specifically is missing this data - this prop makes it available. - // Bit of a hacky fix, but sufficient to allow for manual workarounds. - socket.__httpPeekedData = data; - - this.__httpSocketHandler(socket); + serverhttp.addListener("upgrade", upgradeListener); + serverspdy.addListener("upgrade", upgradeListener); + serverhttp.addListener("request", requestListener); + serverspdy.addListener("request", requestListener); + function handletls(socket) { + serverspdy.emit("connection", socket); } - } -}; - -exports.Server = Server; - -exports.createServer = function (tlsconfig, requestListener) { - return new Server(tlsconfig, requestListener); -}; + function handlehttp(socket) { + serverhttp.emit("connection", socket); + } + servernet.addListener("connection", connectionListener); + function connectionListener(socket) { + assert(Reflect.get(socket, "allowHalfOpen") === false); + if (!socket.listeners("error").length) { + socket.on("error", () => {}); + } + const data = socket.read(1); + if (data === null) { + socket.once("readable", () => { + connectionListener(socket); + }); + } else { + const firstByte = data[0]; + socket.unshift(data); + if (firstByte === 22) { + handletls(socket); + } else if (32 < firstByte && firstByte < 127) { + handlehttp(socket); + } else { + socket.destroy(); + } + } + } + return servernet; +} diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 0000000..7ccd163 --- /dev/null +++ b/lib/index.ts @@ -0,0 +1,104 @@ +import assert from "assert"; +import http from "http"; +import net from "net"; +import spdy from "spdy"; +import tls from "tls"; +import { + RequestListener, + requestNotFound, + ServerOptions, + UpgradeListener, + upgradeNotFound, +} from "./declaration.js"; +export * from "./declaration.js"; +export { createServer }; + +function createServer( + config: ServerOptions, + requestListener: RequestListener = requestNotFound, + upgradeListener: UpgradeListener = upgradeNotFound +): net.Server { + if (!(config && typeof config === "object")) { + throw new Error("options are required!"); + } + requestListener = requestListener || requestNotFound; + upgradeListener = upgradeListener || upgradeNotFound; + + const servernet = net.createServer(config); + const serverhttp = http.createServer(config); + //@ts-ignore + const serverspdy = spdy.createServer(config); + servernet.addListener("error", () => {}); + serverhttp.addListener("ClientError", (err: Error, socket: net.Socket) => { + socket.destroy(); + }); + serverhttp.addListener("error", () => {}); + serverspdy.addListener( + "tlsClientError", + (err: Error, socket: tls.TLSSocket) => { + socket.destroy(); + } + ); + serverspdy.addListener("error", () => {}); + serverspdy.prependListener("secureConnection", (socket: tls.TLSSocket) => { + if (!socket.listeners("error").length) { + socket.on("error", () => {}); + } + }); + serverhttp.addListener("upgrade", upgradeListener); + serverspdy.addListener("upgrade", upgradeListener); + serverhttp.addListener("request", requestListener); + serverspdy.addListener("request", requestListener); + /* 修复bug + 程序没有监听套接字上的error事件,然后程序崩溃了 +net.Socket +tls.TLSSocket +自动监听error事件,防止服务器意外退出 +*/ + function handletls(socket: net.Socket) { + serverspdy.emit("connection", socket); + } + function handlehttp(socket: net.Socket) { + serverhttp.emit("connection", socket); + } + // serverspdy.addListener("connection", connectionListener); + servernet.addListener("connection", connectionListener); + function connectionListener(socket: net.Socket) { + assert(Reflect.get(socket, "allowHalfOpen") === false); + /* 类型“Socket”上不存在属性“allowHalfOpen” */ + // socket.allowHalfOpen = false; + //如果没有error监听器就添加error 监听器 + if (!socket.listeners("error").length) { + socket.on("error", () => {}); + } + // let ishttp = false; + // let istls = false; + + const data = socket.read(1); + /* https://github.com/httptoolkit/httpolyglot/blob/master/lib/index.js */ + if (data === null) { + socket.once("readable", () => { + connectionListener(socket); + }); + } else { + const firstByte = data[0]; + socket.unshift(data); + if (firstByte === 22) { + //默认已经是false了 + //// TLS sockets don't allow half open + // socket.allowHalfOpen = false; + handletls(socket); + // istls = true; + } else if (32 < firstByte && firstByte < 127) { + // ishttp = true; + + handlehttp(socket); + } else { + socket.destroy(); + } + } + /* 测试发现不能使用on data事件,会收不到响应,多次数据会漏掉 */ + } + + return servernet; +} diff --git a/package.json b/package.json index fe38f16..d106cd3 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,54 @@ { - "name": "@httptoolkit/httpolyglot", - "version": "0.2.0", - "author": "Tim Perry ", - "description": "Serve http and https connections over the same port with node.js", - "main": "./lib/index.js", - "scripts": { - "test": "node test/test.js" - }, - "engines": { - "node": ">=8.0.0" - }, - "keywords": [ - "http", - "https", - "multiplex", - "polyglot" - ], - "licenses": [ - { - "type": "MIT", - "url": "http://github.com/httptoolkit/httpolyglot/raw/master/LICENSE" - } - ], - "repository": { - "type": "git", - "url": "http://github.com/httptoolkit/httpolyglot.git" - } + "type": "module", + "types": "./dist/index.d.ts", + "name": "@masx200/http-https-spdy-http2-polyglot", + "version": "1.0.4", + "main": "./lib/index.js", + "scripts": { + "test": "node --experimental-modules ./test/test.js ", + "open": "node --experimental-modules ./test/open.js", + "build": "tsc&& api-extractor run --local", + "serve": "node --experimental-modules ./test/serve.js", + "prettier": "prettier --write *.md *.json ./**/*.js */**.ts" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "spdy": "^4.0.2" + }, + "keywords": [ + "http", + "https", + "multiplex", + "polyglot", + "http2" + ], + "dependencies": { + "@types/node": "^13.13.0", + "@types/spdy": "^3.4.4" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.7.13", + "@types/ws": "^7.2.4", + "fetch-h2": "^2.4.5", + "node-fetch": "^3.0.0-beta.4", + "open": "^7.0.3", + "prettier": "^2.0.4", + "spdy": "^4.0.2", + "tslib": "^1.11.1", + "typescript": "^3.8.3", + "ws": "^7.2.3" + }, + "license": "ISC", + "repository": { + "type": "git", + "url": "git+https://github.com/masx200/http-https-spdy-http2-polyglot.git" + }, + "author": "masx200 <34191203+masx200@users.noreply.github.com>", + "bugs": { + "url": "https://github.com/masx200/http-https-spdy-http2-polyglot/issues" + }, + "homepage": "https://github.com/masx200/http-https-spdy-http2-polyglot#readme", + "description": "Serve http and https and spdy and http2 connections over the same port with node.js" } diff --git a/test/common.js b/test/common.js deleted file mode 100644 index 3c579c0..0000000 --- a/test/common.js +++ /dev/null @@ -1,43 +0,0 @@ -exports.mustCall = mustCall; -function mustCall(fn, expected) { - if (typeof expected !== 'number') - expected = 1; - - var context = { - expected: expected, - actual: 0, - stack: (new Error()).stack, - name: fn.name || '' - }; - - // add the exit listener only once to avoid listener leak warnings - if (mustCall.checks.length === 0) - process.on('exit', mustCall.runChecks); - - mustCall.checks.push(context); - - return function() { - context.actual++; - return fn.apply(this, arguments); - }; -} -mustCall.checks = []; -mustCall.runChecks = function(exitCode) { - if (exitCode !== 0) - return; - - var failed = mustCall.checks.filter(function(context) { - return context.actual !== context.expected; - }); - - failed.forEach(function(context) { - console.log('Mismatched %s function calls. Expected %d, actual %d.', - context.name, - context.expected, - context.actual); - console.log(context.stack.split('\n').slice(2).join('\n')); - }); - - if (failed.length) - process.exit(1); -}; diff --git a/test/early-client-disconnect.js b/test/early-client-disconnect.js new file mode 100644 index 0000000..73eae81 --- /dev/null +++ b/test/early-client-disconnect.js @@ -0,0 +1,36 @@ +import assert from "assert"; +import net from "net"; +import { createServer } from "../lib/index.js"; +import { cert, key } from "./key-cert.js"; +const port = 0; +// @ts-ignore + +const server = createServer( + { + key, + cert, + }, + async function (req, res) { + assert(false, "Request handler should not be called"); + } +); + +server.listen(port, "localhost", function () { + var port = this.address().port; + console.log("httpolyglot server listening on port " + port); + const socket = net.connect(port, "localhost", () => { + console.log("client connect"); + }); + socket.on("error", console.error); + socket.write(Buffer.from([0])); + + socket.end(() => { + console.log("client end"); + }); + socket.on("close", (e) => { + console.log("client close", e); + server.close(() => { + console.log("server close"); + }); + }); +}); diff --git a/test/early-server-disconnect.js b/test/early-server-disconnect.js new file mode 100644 index 0000000..74752c0 --- /dev/null +++ b/test/early-server-disconnect.js @@ -0,0 +1,30 @@ +import assert from "assert"; +import net from "net"; +import { createServer } from "../lib/index.js"; +import { cert, key } from "./key-cert.js"; +const port = 0; +// @ts-ignore + +const server = createServer( + { + key, + cert, + }, + async function (req, res) { + assert(false, "Request handler should not be called"); + } +); + +server.listen(port, "localhost", function () { + var port = this.address().port; + console.log("httpolyglot server listening on port " + port); + const socket = net.connect(port, "localhost", () => { + console.log("client connect"); + server.close(() => { + console.log("server close"); + }); + }); + socket.on("close", (e) => { + console.log("client close", e); + }); +}); diff --git a/test/fetch1.js b/test/fetch1.js new file mode 100644 index 0000000..e8ec64e --- /dev/null +++ b/test/fetch1.js @@ -0,0 +1,39 @@ +import https from "https"; +import fetch1 from "node-fetch"; +import { cert } from "./key-cert.js"; +import { urls } from "./urls.js"; +import { logjson } from "./logjson.js"; +import { formatresponse } from "./format-response.js"; + +process.on("unhandledRejection", console.error); + +const fetch = + /** + * @param {string} url + * @param {Object} opt + */ + function (url, opt) { + const agent = new https.Agent({ + ca: cert, + }); + // @ts-ignore + return fetch1.default(url, { + agent: url.startsWith("http:") ? undefined : agent, + ...opt, + }); + }; + +// @ts-ignore + +~((fetch) => { + Promise.allSettled( + urls.map(async (url) => { + return fetch(url, { timeout: 2000, redirect: "manual" }).then( + async (r) => { + return formatresponse(r); + } + ); + }) + ).then(logjson); + // @ts-ignore +})(fetch); diff --git a/test/fetch2.js b/test/fetch2.js new file mode 100644 index 0000000..494191f --- /dev/null +++ b/test/fetch2.js @@ -0,0 +1,24 @@ +import fetch2 from "fetch-h2"; +import { cert } from "./key-cert.js"; +import { urls } from "./urls.js"; +import { formatresponse } from "./format-response.js"; +import { logjson } from "./logjson.js"; + +process.on("unhandledRejection", console.error); + +const fetch = fetch2.context({ session: { ca: cert } }).fetch; + +// @ts-ignore + +~((fetch) => { + Promise.allSettled( + urls.map(async (url) => { + return fetch(url, { timeout: 2000, redirect: "manual" }).then( + async (r) => { + return formatresponse(r); + } + ); + }) + ).then(logjson); + // @ts-ignore +})(fetch); diff --git a/test/fixtures/server.crt b/test/fixtures/server.crt deleted file mode 100644 index 0c6b514..0000000 --- a/test/fixtures/server.crt +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDjzCCAnegAwIBAgIJANiEfJkuqhEaMA0GCSqGSIb3DQEBCwUAMF4xCzAJBgNV -BAYTAlVTMQ0wCwYDVQQIDARPaGlvMQ8wDQYDVQQHDAZEYXl0b24xEjAQBgNVBAoM -CUZvbywgSW5jLjEbMBkGA1UEAwwSbXlob3N0LmxvY2FsZG9tYWluMB4XDTE2MTEy -NjE3MTUxNFoXDTE2MTIyNjE3MTUxNFowXjELMAkGA1UEBhMCVVMxDTALBgNVBAgM -BE9oaW8xDzANBgNVBAcMBkRheXRvbjESMBAGA1UECgwJRm9vLCBJbmMuMRswGQYD -VQQDDBJteWhvc3QubG9jYWxkb21haW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw -ggEKAoIBAQDi8OEKkQogjasE908B/yBHqYFtbYfYJJpq3qsxV7oWWLuygEvVTM/O -edAt0CrXppgh1VxKIbyxkQtf+2TyoUlWxUK/9iYzXolTMQ82X+WeeClPdemoNIYV -wCKrZObJ1TtBM8v2mL8NfQZumr9NahOfK6pWWuLosKtXviJuzAVKQCm3r0scbzi/ -avSP63LfY/ua7m29PZI4wflLAghAUbNAaefp9UAxgYQigXuIj+lMDhIdfuGEndS+ -xkcW/tqeRr42r4xh1C7lb/FagFqMXA5+wDMn1Tj0JBzQLZMR4Ea2vj24BXuPuA8P -Kq6wvbpZ4B3k+3jrWNL2i3mPUFFXriP3AgMBAAGjUDBOMB0GA1UdDgQWBBSuHVhn -CAGxnAE81k3uq1oC+TIRsTAfBgNVHSMEGDAWgBSuHVhnCAGxnAE81k3uq1oC+TIR -sTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCTk27sW0x9CRPC9ZQs -FuhhcJQb7KXxhUikctkQC101+xZHE0VZeBLgkHCr8Q1tRzqJuvQO6Q5CRB8b5DIX -ZHOEtzjNnm8yoMDCWVb/4G8repGacpHDkZv3jZguHxpTYVJlwvdDCr0SGqfpcF4e -5MY8DgfsmXo2hqKp/FozTA1MKs3esuxdrZBxhvoQpRsvX0mxfiBNoTWVu2BgjeqP -11vSPK1z2YCxHkt1SvwrpycfGV8x7qNvEIn+zzxPitYJHro0w0WX8aelT7Xtj2mD -pkLsYhM1dHZkIc+uXVSv+EUUkfLJ1FDJ5D+yyKifyYtBVEOvrDpfUFNKy+bZRYsy -Lcdy ------END CERTIFICATE----- diff --git a/test/fixtures/server.key b/test/fixtures/server.key deleted file mode 100644 index 8f7dfc9..0000000 --- a/test/fixtures/server.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDi8OEKkQogjasE -908B/yBHqYFtbYfYJJpq3qsxV7oWWLuygEvVTM/OedAt0CrXppgh1VxKIbyxkQtf -+2TyoUlWxUK/9iYzXolTMQ82X+WeeClPdemoNIYVwCKrZObJ1TtBM8v2mL8NfQZu -mr9NahOfK6pWWuLosKtXviJuzAVKQCm3r0scbzi/avSP63LfY/ua7m29PZI4wflL -AghAUbNAaefp9UAxgYQigXuIj+lMDhIdfuGEndS+xkcW/tqeRr42r4xh1C7lb/Fa -gFqMXA5+wDMn1Tj0JBzQLZMR4Ea2vj24BXuPuA8PKq6wvbpZ4B3k+3jrWNL2i3mP -UFFXriP3AgMBAAECggEARjVFOdKjMm0Bkpi8DZ8TKnhrPSJcm2a/iv52Md61CELN -VqzQSR3pUDRpTjMPfgXhHN54HcsQKFL6FOieU13IZZrDSsXpDY1aqK0NysGiNQNx -rE6LSelt7f6x+xpNN/XKziIrIJAi0xZxzff75QRDK8QDf5HAj0JQz+VXm7VskYpw -90YdekO5S/7V8UlLDDQFcKVdsmiPoDogPz6C2CnzD/yVzduJhmoM9PqccRyA/WVD -YHejPs+LGhM5SLQaMmMWXX5oQ21Gc/t1xvrHokiqcBpxz1djT8I4amRg7D+3bsNG -d/cRLTKLj1joxJl2VQi+cXT45P5VF5inLwXvraJomQKBgQD0F9FcwhbINjtHFSsr -gexUjwynvAZyNkI+rROku37wBtT4EwMkRgq9FN6aHZZ8wi6kxRVH7KbmGKXmPkIw -H/t2lHsae7GZ207H9LZa+VLfDMYCSXa40SvGRBrSPOrTOMCc3HgmP0s/00xfALzs -T4IHJbOYwhzArZhwC6YLn9byFQKBgQDuAt6QxiJneUM/1JW5P7TEzGLi9bwUmSKH -EkLbsDKRmqH1EvEoQaG9js9FBLBKaSRYRnub3FbRb7Ckk1MpQY67rOb8L2ypp7uy -+Bv2Cmbm4mD+/Aapw5Is/6UqNCqTChjI58437eEtZx7X4cYBLwiaKPvPUWI6L2wJ -4nMsr3fc2wKBgQCpaqqelfvYBIQKJzAqZ2fPnOXsub1DolNCS0CaEqTdFfDVKeUB -VTf42rZSA31CpEhZhozpueBxTeQ/tTCdVGVlfVMgI4A2SJgagsfaxrf1Jll8lt63 -Ej8uwnBXQX6/EeHmPcOK0F17ND4Kpml6HwkhytInkXsBZLur8PnTkaJPrQKBgQCN -kF9YtMBZ0yJQoNy85ktakkZuv8IybjK/K/lgOZiaSeLypWWSkBbnbD2Ty4ofeBIJ -/0IeHhv1Tf0+pfHcpAWFUv3AGWUEM6PMew4GdYFm6lbO0pAUASK8aQGP7J81/ddo -B5f8ZBx+qMsLlFn08kiniKDdWoaWHQahinL+rQ8Z6QKBgQDN1SpQnFHYy3uugmN0 -KZjDjVs9gBo0lhNVE/HUbf91TiY/ITpjUCHmv+N3VxKyCII4O7+jjFhzK+5NKLV2 -WsYLN9ttR/2FQ4JnTUocXple3DxmaA7rCRZj8ZQ/jrGwbEj5nLyOIDblWgaaADMB -W2YC7grwofz3HqqSUCHySDHvUA== ------END PRIVATE KEY----- diff --git a/test/format-response.js b/test/format-response.js new file mode 100644 index 0000000..199c18a --- /dev/null +++ b/test/format-response.js @@ -0,0 +1,6 @@ +/** + * @param {{ url: any; status: any; headers: any; text: () => any; }} r + */ +export function formatresponse(r) { + return Promise.all([r.url, r.status, [...r.headers], r.text()]); +} diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..02d608b --- /dev/null +++ b/test/index.js @@ -0,0 +1,23 @@ +let url = new URL(location.href); +if (url.protocol === "https:") { + url.protocol = "wss:"; +} else if (url.protocol === "http:") { + url.protocol = "ws:"; +} + +let socket = new WebSocket(url.href); +/** + * @param {string} message + */ +function appendtodom(message) { + let p = document.createElement("p"); + p.innerText = message; + document.body.appendChild(p); +} +socket.addEventListener("close", console.log); +socket.addEventListener("open", console.log); +socket.addEventListener("error", console.log); +socket.addEventListener("message", console.log); +socket.addEventListener("message", (e) => { + appendtodom(e.data); +}); diff --git a/test/key-cert.js b/test/key-cert.js new file mode 100644 index 0000000..5b01d2b --- /dev/null +++ b/test/key-cert.js @@ -0,0 +1,14 @@ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { dirname } from "path"; +// @ts-ignore +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +export const key = fs.readFileSync( + path.join(__dirname, "../", "./server.key.pem") +); +export const cert = fs.readFileSync( + path.join(__dirname, "../", "./server.crt.pem") +); diff --git a/test/logjson.js b/test/logjson.js new file mode 100644 index 0000000..e62fd96 --- /dev/null +++ b/test/logjson.js @@ -0,0 +1,6 @@ +/** + * @param {any} o + */ +export function logjson(o) { + console.log(JSON.stringify(o, null, 4)); +} diff --git a/test/notfound.js b/test/notfound.js new file mode 100644 index 0000000..c2ead69 --- /dev/null +++ b/test/notfound.js @@ -0,0 +1,13 @@ +import { createServer } from "../lib/index.js"; +import { cert, key } from "./key-cert.js"; +const port = 8998; +// @ts-ignore + +const server = createServer({ + key, + cert, +}); + +server.listen(port, "localhost", function () { + console.log("httpolyglot server listening on port " + port); +}); diff --git a/test/open.js b/test/open.js new file mode 100644 index 0000000..250dd30 --- /dev/null +++ b/test/open.js @@ -0,0 +1,5 @@ +import open from "open"; +import { urls } from "./urls.js"; + +Promise.all(urls.map((url) => open(url))).then((p) => console.log(p)); +process.on("unhandledRejection", console.error); diff --git a/test/push.js b/test/push.js new file mode 100644 index 0000000..8ca3d6d --- /dev/null +++ b/test/push.js @@ -0,0 +1,43 @@ +import { createServer } from "../lib/index.js"; +import { cert, key } from "./key-cert.js"; +const port = 9002; +// @ts-ignore + +const server = createServer( + { + key, + cert, + }, + (req, res) => { + if (req.url == "/main.js") { + res.statusCode = 200; + res.setHeader("content-type", "application/javascript"); + res.end('alert("not from push stream")'); + return; + } else { + res.writeHead(200, { "Content-Type": "text/html" }); + + if (res.push) { + var stream = res.push("/main.js", { + status: 200, // optional + method: "GET", // optional + request: { + accept: "*/*", + }, + response: { + "content-type": "application/javascript", + }, + }); + stream.on("error", function (e) { + console.log(e); + }); + stream.end('alert("hello from push stream!");'); + } + + res.end('push'); + } + } +); +server.listen(port, "localhost", function () { + console.log("httpolyglot server listening on port " + port); +}); diff --git a/test/redirect.js b/test/redirect.js new file mode 100644 index 0000000..e022508 --- /dev/null +++ b/test/redirect.js @@ -0,0 +1,32 @@ +import { createServer } from "../lib/index.js"; +import { cert, key } from "./key-cert.js"; +// @ts-ignore + +const port = 9001; +const server = createServer( + { + key, + cert, + }, + function (req, res) { + if ("encrypted" in req.socket) { + res.writeHead(200, { "Content-Type": "text/html" }); + res.end("Welcome, HTTPS user!"); + } else { + const host = req.headers["host"]; + const originurl = req.url || ""; + const tourl = new URL(originurl, "https://" + host); + tourl.port = String(port); + res.writeHead(302, { + Location: tourl.href, + "Content-Type": "text/html", + }); + res.write("302"); + return res.end(); + } + } +); + +server.listen(port, "localhost", function () { + console.log("httpolyglot server listening on port " + port); +}); diff --git a/test/serve.js b/test/serve.js new file mode 100644 index 0000000..4b419e1 --- /dev/null +++ b/test/serve.js @@ -0,0 +1,6 @@ +import "./notfound.js"; +import "./simple.js"; +import "./push.js"; +import "./redirect.js"; +import "./websocket.js"; +process.on("unhandledRejection", console.error); diff --git a/test/simple.js b/test/simple.js new file mode 100644 index 0000000..6b49f9f --- /dev/null +++ b/test/simple.js @@ -0,0 +1,25 @@ +import { createServer } from "../lib/index.js"; +import { cert, key } from "./key-cert.js"; +const port = 9000; + +const server = createServer( + { + key, + cert, + }, + async function (req, res) { + res.writeHead(200, { "Content-Type": "text/html" }); + const body = + ("encrypted" in req.socket ? "HTTPS" : "HTTP") + + " Connection!\n" + + "alpnProtocol:" + + req.socket.alpnProtocol + + " \n"; + + res.end(body); + } +); + +server.listen(port, "localhost", function () { + console.log("httpolyglot server listening on port " + port); +}); diff --git a/test/test-early-disconnect.js b/test/test-early-disconnect.js deleted file mode 100755 index 7c1dd87..0000000 --- a/test/test-early-disconnect.js +++ /dev/null @@ -1,21 +0,0 @@ -var fs = require('fs'); -var exec = require('child_process').exec; -var assert = require('assert'); - -var common = require(__dirname + '/common'); -var httpolyglot = require(__dirname + '/../lib/index'); - -var srv = httpolyglot.createServer({ - key: fs.readFileSync(__dirname + '/fixtures/server.key'), - cert: fs.readFileSync(__dirname + '/fixtures/server.crt') -}, function(req, res) { - assert(false, 'Request handler should not be called'); -}); -srv.listen(0, '127.0.0.1', common.mustCall(function() { - var port = this.address().port; - - exec('nmap 127.0.0.1 -p' + port, - common.mustCall(function(err, stdout, stderr) { - srv.close(); - })); -})); diff --git a/test/test-normal.js b/test/test-normal.js deleted file mode 100755 index d3c96ac..0000000 --- a/test/test-normal.js +++ /dev/null @@ -1,45 +0,0 @@ -var fs = require('fs'); -var http = require('http'); -var https = require('https'); -var assert = require('assert'); - -var common = require(__dirname + '/common'); -var httpolyglot = require(__dirname + '/../lib/index'); - -var srv = httpolyglot.createServer({ - key: fs.readFileSync(__dirname + '/fixtures/server.key'), - cert: fs.readFileSync(__dirname + '/fixtures/server.crt') -}, common.mustCall(function(req, res) { - this.count || (this.count = 0); - res.end(req.socket.encrypted ? 'https' : 'http'); - if (++this.count === 2) - this.close(); -}, 2)); -srv.listen(0, '127.0.0.1', common.mustCall(function() { - var port = this.address().port; - - http.get({ - host: '127.0.0.1', - port: port - }, common.mustCall(function(res) { - var body = ''; - res.on('data', function(data) { - body += data; - }).on('end', common.mustCall(function() { - assert.strictEqual(body, 'http'); - })); - })); - - https.get({ - host: '127.0.0.1', - port: port, - rejectUnauthorized: false - }, common.mustCall(function(res) { - var body = ''; - res.on('data', function(data) { - body += data; - }).on('end', common.mustCall(function() { - assert.strictEqual(body, 'https'); - })); - })); -})); diff --git a/test/test.js b/test/test.js old mode 100755 new mode 100644 index b2b0dba..7341eee --- a/test/test.js +++ b/test/test.js @@ -1,4 +1,26 @@ -require('fs').readdirSync(__dirname).forEach(function(f) { - if (f.substr(0, 5) === 'test-') - require('./' + f); -}); +import { execFile } from "child_process"; +// @ts-ignore +import path, { dirname } from "path"; +import { fileURLToPath } from "url"; +import util from "util"; +process.on("unhandledRejection", console.error); +// @ts-ignore +const execpro = util.promisify(execFile); +// @ts-ignore +const __filename = fileURLToPath(import.meta.url); +var __dirname = dirname(__filename); +const files = [ + "tls-early-disconnect.js", + "early-server-disconnect.js", + "early-client-disconnect.js", + "fetch1.js", + "fetch2.js", +].map((p) => path.join(__dirname, p)); +files.reduce(async (prev, file) => { + await prev; + const result = await execpro("node", [file]); + const { stdout, stderr } = result; + console.log(file); + console.log(stdout); + console.log(stderr); +}, Promise.resolve()); diff --git a/test/tls-early-disconnect.js b/test/tls-early-disconnect.js new file mode 100644 index 0000000..5aa5be5 --- /dev/null +++ b/test/tls-early-disconnect.js @@ -0,0 +1,57 @@ +import assert from "assert"; +import tls from "tls"; +import { createServer } from "../lib/index.js"; +import { cert, key } from "./key-cert.js"; + +const port = 0; +// @ts-ignore + +const server = createServer( + { + key, + cert, + }, + async function (req, res) { + assert(false, "Request handler should not be called"); + } +); +const host = "localhost"; +server.listen(port, host, function () { + var port = this.address().port; + console.log("httpolyglot server listening on port " + port); + + const socket = tls.connect({ + port: port, + host: host, + ca: cert, + timeout: 2000, + }); + socket.on("session", (session) => { + console.log("client session", session); + }); + socket.on("secureConnect", () => { + console.log("client connect"); + socket.end(); + socket.destroy(); + }); + socket.on("close", (e) => { + console.log("client close", e); + server.close((e) => { + console.log("server close", e); + }); + }); + // socket.on("error", console.error); + // socket.connect(port, "localhost", () => { + // console.log("client connect"); + // socket.end(() => { + // console.log("client end"); + // }); + // socket.destroy(); + // socket.on("close", (e) => { + // console.log("client close", e); + // server.close(() => { + // console.log("server close"); + // }); + // }); + // }); +}); diff --git a/test/urls.js b/test/urls.js new file mode 100644 index 0000000..ca0b22d --- /dev/null +++ b/test/urls.js @@ -0,0 +1,8 @@ +const ports = [8998, 9002, 9001, 9000, 8999]; +const host = "localhost"; +const protocols = ["https:", "http:"]; +const urls = protocols + .map((protocol) => ports.map((port) => `${protocol}//${host}:${port}/`)) + // @ts-ignore + .flat(1 / 0); +export { urls }; diff --git a/test/websocket.js b/test/websocket.js new file mode 100644 index 0000000..e739d42 --- /dev/null +++ b/test/websocket.js @@ -0,0 +1,52 @@ +import fs from "fs"; +import path, { dirname } from "path"; +import { fileURLToPath } from "url"; +import ws from "ws"; +import { createServer } from "../lib/index.js"; +import { cert, key } from "./key-cert.js"; +const port = 8999; +const wsServer = new ws.Server({ noServer: true }); +wsServer.on("connection", (websocket, req) => { + websocket.send(JSON.stringify(req.headers)); + websocket.send( + ("encrypted" in req.socket ? "HTTPS" : "HTTP") + " Connection!" + ); +}); +// @ts-ignore +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const server = createServer( + { + key, + cert, + }, + async function (req, res) { + if (req.url === "/") { + res.writeHead(200, { "Content-Type": "text/html" }); + + res.end( + "websocket" + ); + } else if (req.url === "/index.js") { + res.writeHead(200, { "Content-Type": "text/javascript" }); + const jsfile = await fs.promises.readFile( + path.join(__dirname, "index.js") + ); + res.write(jsfile); + res.end(); + } else { + res.statusCode = 404; + res.write("404"); + res.end(); + } + }, + function (req, socket, head) { + wsServer.handleUpgrade(req, socket, head, function done(ws) { + wsServer.emit("connection", ws, req); + }); + } +); + +server.listen(port, "localhost", function () { + console.log("httpolyglot server listening on port " + port); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e4eb52f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "include": ["*/**.ts"], + "compilerOptions": { + "baseUrl": ".", + "paths": {}, + "pretty": true, + "removeComments": true, + "moduleResolution": "node", + "resolveJsonModule": false, + "declaration": true, + "declarationMap": true, + "allowJs": true, + "checkJs": false, + "charset": "utf-8", + "strict": true, + "target": "es2019", + "module": "ESNEXT", + "importHelpers": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..e8e3b6c --- /dev/null +++ b/yarn.lock @@ -0,0 +1,519 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@microsoft/api-extractor-model@7.7.11": + version "7.7.11" + resolved "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.7.11.tgz#9dfc5425f4a6a2b6b1ebc39332ae8101ab8da36a" + integrity sha512-Kf3RytYDq7sP4ASaaA9IcvaOPbVj1Xj34E2Wxd9DznI7sG4HzcpoOGmxaZHCzyYVh7wfAaAlvcXf3SV+djhNZw== + dependencies: + "@microsoft/tsdoc" "0.12.19" + "@rushstack/node-core-library" "3.19.7" + +"@microsoft/api-extractor@^7.7.13": + version "7.7.13" + resolved "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.7.13.tgz#7ee70978825a36d294d26e50b5d888ca3789d1b0" + integrity sha512-7+EGunGd7OSfDxZl4aDI4qMWG98+I84vTryNadti+IfgpB9IyhJtgA4r3dCG7hewTwKACBhi5aPqpH2C7l/30A== + dependencies: + "@microsoft/api-extractor-model" "7.7.11" + "@microsoft/tsdoc" "0.12.19" + "@rushstack/node-core-library" "3.19.7" + "@rushstack/ts-command-line" "4.3.14" + colors "~1.2.1" + lodash "~4.17.15" + resolve "1.8.1" + source-map "~0.6.1" + typescript "~3.7.2" + +"@microsoft/tsdoc@0.12.19": + version "0.12.19" + resolved "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.19.tgz#2173ccb92469aaf62031fa9499d21b16d07f9b57" + integrity sha512-IpgPxHrNxZiMNUSXqR1l/gePKPkfAmIKoDRP9hp7OwjU29ZR8WCJsOJ8iBKgw0Qk+pFwR+8Y1cy8ImLY6e9m4A== + +"@rushstack/node-core-library@3.19.7": + version "3.19.7" + resolved "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.19.7.tgz#8d8a193fd6f99536c92dd797ab50fd5fcb7630ea" + integrity sha512-gKE/OXH5GAj8yJ1kEyRW68UekJernilZ3QTRgmQ0MUHBCQmtZ9Q6T5PQ1sVbcL4teH8BMdpZeFy1DKnHs8h3PA== + dependencies: + "@types/node" "10.17.13" + colors "~1.2.1" + fs-extra "~7.0.1" + jju "~1.4.0" + semver "~5.3.0" + timsort "~0.3.0" + z-schema "~3.18.3" + +"@rushstack/ts-command-line@4.3.14": + version "4.3.14" + resolved "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.3.14.tgz#5d7a437d4e9c564ff1b8e876215fca96c74858a1" + integrity sha512-YJZIyKvkm3f6ZdKSfMntHS9542Y2mmMWzaiPPoXxLFZntKxEIDE3WfUNlvOSo3yK4fNd09Tz3hfvTivQNHSiKQ== + dependencies: + "@types/argparse" "1.0.33" + argparse "~1.0.9" + colors "~1.2.1" + +"@types/argparse@1.0.33": + version "1.0.33" + resolved "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.33.tgz#2728669427cdd74a99e53c9f457ca2866a37c52d" + integrity sha512-VQgHxyPMTj3hIlq9SY1mctqx+Jj8kpQfoLvDlVSDNOyuYs8JYfkuY3OW/4+dO657yPmNhHpePRx0/Tje5ImNVQ== + +"@types/node@*", "@types/node@^13.13.0": + version "13.13.0" + resolved "https://registry.npmjs.org/@types/node/-/node-13.13.0.tgz#30d2d09f623fe32cde9cb582c7a6eda2788ce4a8" + integrity sha512-WE4IOAC6r/yBZss1oQGM5zs2D7RuKR6Q+w+X2SouPofnWn+LbCqClRyhO3ZE7Ix8nmFgo/oVuuE01cJT2XB13A== + +"@types/node@10.17.13": + version "10.17.13" + resolved "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz#ccebcdb990bd6139cd16e84c39dc2fb1023ca90c" + integrity sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg== + +"@types/spdy@^3.4.4": + version "3.4.4" + resolved "https://registry.npmjs.org/@types/spdy/-/spdy-3.4.4.tgz#3282fd4ad8c4603aa49f7017dd520a08a345b2bc" + integrity sha512-N9LBlbVRRYq6HgYpPkqQc3a9HJ/iEtVZToW6xlTtJiMhmRJ7jJdV7TaZQJw/Ve/1ePUsQiCTDc4JMuzzag94GA== + dependencies: + "@types/node" "*" + +"@types/tough-cookie@^2.3.6": + version "2.3.7" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.7.tgz#979434b5900f9d710f5d4e15c466cadb8e9fdc47" + integrity sha512-rMQbgMGxnLsdn8e9aPVyuN+zMQLrZ2QW8xlv7eWS1mydfGXN+tsTKffcIzd8rGCcLdmi3xvQw2MDaZI1bBNTaw== + +"@types/ws@^7.2.4": + version "7.2.4" + resolved "https://registry.npmjs.org/@types/ws/-/ws-7.2.4.tgz#b3859f7b9c243b220efac9716ec42c716a72969d" + integrity sha512-9S6Ask71vujkVyeEXKxjBSUV8ZUB0mjL5la4IncBoheu04bDaYyUKErh1BQcY9+WzOUOiKqz/OnpJHYckbMfNg== + dependencies: + "@types/node" "*" + +already@^1.12.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/already/-/already-1.12.0.tgz#50f63ed461a62be5dd37d05795766bf0e685f3c6" + integrity sha512-JOq+vzTKA3qheL4rOcTYvdUwUhLFrUSiRshlDxxj+7CJTQlw9qHGveuMzEbyGRfoOscREA1gqJxxTnK7GDAfxQ== + dependencies: + throat "^5.0.0" + +argparse@~1.0.9: + version "1.0.10" + resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +buffer-from@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +callguard@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/callguard/-/callguard-1.2.1.tgz#3d8df2ea91c946abf1c43b7d6abde23d3aecfdbb" + integrity sha512-QTxtuE+QrPA/ZStfYEf/IL1RqyOEpg3CMKp35oTs4n91WveYjbnbPJ0O2ob+RxhPzOuvb0KgQvYwJzhZ6nswXQ== + +colors@~1.2.1: + version "1.2.5" + resolved "https://registry.npmjs.org/colors/-/colors-1.2.5.tgz#89c7ad9a374bc030df8013241f68136ed8835afc" + integrity sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg== + +commander@^2.7.1: + version "2.20.3" + resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +data-uri-to-buffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.0.tgz#8a3088a5efd3f53c3682343313c6895d498eb8d7" + integrity sha512-MJ6mFTZ+nPQO+39ua/ltwNePXrfdF3Ww0wP1Od7EePySXN1cP9XNqRQOG3FxTfipp8jx898LUCgBCEP11Qw/ZQ== + dependencies: + buffer-from "^1.1.1" + +debug@^4.1.0: + version "4.1.1" + resolved "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +detect-node@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" + integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +fetch-blob@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-1.0.5.tgz#5cc86a8740236c38d8073b153c8b51be5f3dc11e" + integrity sha512-BIggzO037jmCrZmtgntzCD2ymEaWgw9OMJsfX7FOS1jXGqKW9FEhETJN8QK4KxzIJknRl3RQdyzz34of+NNTMQ== + +fetch-h2@^2.4.5: + version "2.4.5" + resolved "https://registry.yarnpkg.com/fetch-h2/-/fetch-h2-2.4.5.tgz#aeccd1d47e0d0942b0477f857d27c45d4cbb661c" + integrity sha512-Nl6wvKqwHPBKlGbXQsnA55EgzI62p/pTb5ISrfxJEAb7ShwcrITEQHAVHKiuf9Z3ulMd64fkildG/bQB4J3qzw== + dependencies: + "@types/tough-cookie" "^2.3.6" + already "^1.12.0" + callguard "^1.2.1" + get-stream "^5.1.0" + through2 "^3.0.1" + to-arraybuffer "^1.0.1" + tough-cookie "^3.0.1" + +fs-extra@~7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +get-stream@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" + integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== + dependencies: + pump "^3.0.0" + +graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.2.3" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= + +inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ip-regex@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" + integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= + +is-docker@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.0.0.tgz#2cb0df0e75e2d064fe1864c37cdeacb7b2dcf25b" + integrity sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ== + +is-wsl@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d" + integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +jju@~1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" + integrity sha1-o6vicYryQaKykE+EpiWXDzia4yo= + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +lodash.get@^4.0.0: + version "4.4.2" + resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + +lodash.isequal@^4.0.0: + version "4.5.0" + resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + +lodash@~4.17.15: + version "4.17.15" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +node-fetch@^3.0.0-beta.4: + version "3.0.0-beta.4" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.0.0-beta.4.tgz#f5fcc70e506314c90df4f5f19e81cff55aa1ecea" + integrity sha512-NxI746RCCuW5XmvGEOVPMFksmoMFEaWUEhNAGY5VLJ9qmit1mCEfRTHdomZuMe94FuY2QO2DLrWYe70HT7ubqw== + dependencies: + data-uri-to-buffer "^3.0.0" + fetch-blob "^1.0.5" + +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +open@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/open/-/open-7.0.3.tgz#db551a1af9c7ab4c7af664139930826138531c48" + integrity sha512-sP2ru2v0P290WFfv49Ap8MF6PkzGNnGlAwHweB4WR4mr5d2d0woiCluUeJ218w7/+PmoBy9JmYgD5A4mLcWOFA== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + +path-parse@^1.0.5: + version "1.0.6" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +prettier@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/prettier/-/prettier-2.0.4.tgz#2d1bae173e355996ee355ec9830a7a1ee05457ef" + integrity sha512-SVJIQ51spzFDvh4fIbCLvciiDMCrRhlN3mbZvv/+ycjvmF5E73bKdGfU8QDLNmjYJf+lsGnDBC4UUnvTe5OO0w== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +"readable-stream@2 || 3", readable-stream@^3.0.6: + version "3.6.0" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-stream@^2.0.1: + version "2.3.7" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +resolve@1.8.1: + version "1.8.1" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" + integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA== + dependencies: + path-parse "^1.0.5" + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= + +semver@~5.3.0: + version "5.3.0" + resolved "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8= + +source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +throat@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" + integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== + +through2@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.1.tgz#39276e713c3302edf9e388dd9c812dd3b825bd5a" + integrity sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww== + dependencies: + readable-stream "2 || 3" + +timsort@~0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= + +to-arraybuffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= + +tough-cookie@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" + integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== + dependencies: + ip-regex "^2.1.0" + psl "^1.1.28" + punycode "^2.1.1" + +tslib@^1.11.1: + version "1.11.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" + integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== + +typescript@^3.8.3: + version "3.8.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" + integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== + +typescript@~3.7.2: + version "3.7.5" + resolved "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" + integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +validator@^8.0.0: + version "8.2.0" + resolved "https://registry.npmjs.org/validator/-/validator-8.2.0.tgz#3c1237290e37092355344fef78c231249dab77b9" + integrity sha512-Yw5wW34fSv5spzTXNkokD6S6/Oq92d8q/t14TqsS3fAiA1RYnxSFSIZ+CY3n6PGGRCq5HhJTSepQvFUS2QUDxA== + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +ws@^7.2.3: + version "7.2.3" + resolved "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46" + integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ== + +z-schema@~3.18.3: + version "3.18.4" + resolved "https://registry.npmjs.org/z-schema/-/z-schema-3.18.4.tgz#ea8132b279533ee60be2485a02f7e3e42541a9a2" + integrity sha512-DUOKC/IhbkdLKKiV89gw9DUauTV8U/8yJl1sjf6MtDmzevLKOF2duNJ495S3MFVjqZarr+qNGCPbkg4mu4PpLw== + dependencies: + lodash.get "^4.0.0" + lodash.isequal "^4.0.0" + validator "^8.0.0" + optionalDependencies: + commander "^2.7.1"