@httptoolkit/httpolyglot 2.2.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +3 -0
- package/README.md +23 -14
- package/dist/index.d.ts +35 -36
- package/dist/index.js +97 -48
- package/dist/index.js.map +1 -1
- package/package.json +5 -9
- package/src/index.ts +135 -85
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -12,11 +12,13 @@ Forked from the original [`httpolyglot`](https://github.com/mscdex/httpolyglot)
|
|
|
12
12
|
* Dropping support for very old versions of Node (and thereby simplifying the code somewhat)
|
|
13
13
|
* Converting to TypeScript
|
|
14
14
|
* Event subscription support (subscribe to `server.on(x, ...)` to hear about `x` from _all_ internal servers - HTTP/2, HTTP/1, TLS and net)
|
|
15
|
+
* Adding support for SOCKS connections
|
|
16
|
+
* Adding support for custom handling of unknown protocols
|
|
15
17
|
|
|
16
18
|
Requirements
|
|
17
19
|
============
|
|
18
20
|
|
|
19
|
-
* [node.js](http://nodejs.org/) --
|
|
21
|
+
* [node.js](http://nodejs.org/) -- v20 or newer
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
Install
|
|
@@ -31,12 +33,14 @@ Examples
|
|
|
31
33
|
* Simple usage:
|
|
32
34
|
|
|
33
35
|
```javascript
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
import * as httpolyglot from '@httptoolkit/httpolyglot';
|
|
37
|
+
import * as fs from 'fs';
|
|
36
38
|
|
|
37
39
|
httpolyglot.createServer({
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
tls: {
|
|
41
|
+
key: fs.readFileSync('server.key'),
|
|
42
|
+
cert: fs.readFileSync('server.crt')
|
|
43
|
+
}
|
|
40
44
|
}, function(req, res) {
|
|
41
45
|
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
42
46
|
res.end((req.socket.encrypted ? 'HTTPS' : 'HTTP') + ' Connection!');
|
|
@@ -49,12 +53,14 @@ httpolyglot.createServer({
|
|
|
49
53
|
* Simple redirect of all http connections to https:
|
|
50
54
|
|
|
51
55
|
```javascript
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
import * as httpolyglot from '@httptoolkit/httpolyglot';
|
|
57
|
+
import * as fs from 'fs';
|
|
54
58
|
|
|
55
59
|
httpolyglot.createServer({
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
tls: {
|
|
61
|
+
key: fs.readFileSync('server.key'),
|
|
62
|
+
cert: fs.readFileSync('server.crt')
|
|
63
|
+
}
|
|
58
64
|
}, function(req, res) {
|
|
59
65
|
if (!req.socket.encrypted) {
|
|
60
66
|
res.writeHead(301, { 'Location': 'https://localhost:9000' });
|
|
@@ -72,14 +78,17 @@ httpolyglot.createServer({
|
|
|
72
78
|
API
|
|
73
79
|
===
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
* **createServer**([< _object_ >config, ]< _function_ >requestListener) - _Server_ - Creates and returns a new Server instance.
|
|
82
|
+
|
|
83
|
+
If no config is provided, this server handles HTTP/1 & HTTP/2 in plain text on the same port.
|
|
77
84
|
|
|
78
|
-
|
|
85
|
+
If a config is provided, it can contain:
|
|
79
86
|
|
|
80
|
-
|
|
87
|
+
- `tls` - Either TLS configuration options for [`tls.createServer`](https://nodejs.org/api/tls.html#tlscreateserveroptions-secureconnectionlistener), or an existing `tls.Server` instance. Setting this option enables TLS, so that HTTP/1 & HTTP/2 are accepted over both plain-text & encrypted (HTTPS) connections on the same port. If configuration options are provided, Httpolyglot will handle TLS automatically. If a server is provided, the connection will be passed to it (by emitting the `connection` event) and Httpolyglot will wait for the server to emit `secureConnection` (the default TLS server event) to handle the content within.
|
|
88
|
+
- `socks` - A `net.Server` instance, which will handle any incoming SOCKS connections. If this is provided, SOCKSv4 and SOCKSv5 connections will be emitted as `connection` events on this server. If not, all SOCKS connections will be treated like any other unknown protocol.
|
|
89
|
+
- `unknownProtocol` - A `net.Server` instance, which will handle any unknown protocols. If this is provided, unrecognized protocols will be emitted as `connection` events on this server. If it's not provided, these connections will be passed to the HTTP server by default, which will typically result in a `clientError` event and a 400 HTTP response being sent to the client.
|
|
81
90
|
|
|
82
91
|
How it Works
|
|
83
92
|
============
|
|
84
93
|
|
|
85
|
-
TLS
|
|
94
|
+
TLS, HTTP, HTTP/2, SOCKS and other 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.
|
package/dist/index.d.ts
CHANGED
|
@@ -2,60 +2,59 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
/// <reference types="node" />
|
|
4
4
|
/// <reference types="node" />
|
|
5
|
-
/// <reference types="node" />
|
|
6
5
|
import * as net from 'net';
|
|
7
6
|
import * as tls from 'tls';
|
|
8
7
|
import * as http from 'http';
|
|
9
8
|
import * as https from 'https';
|
|
10
|
-
|
|
11
|
-
interface Socket {
|
|
12
|
-
/**
|
|
13
|
-
* Only preserved for types backward compat - always undefined in new releases.
|
|
14
|
-
*
|
|
15
|
-
* @deprecated
|
|
16
|
-
*/
|
|
17
|
-
__httpPeekedData?: Buffer;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
export declare class Server extends net.Server {
|
|
21
|
-
private _httpServer;
|
|
22
|
-
private _http2Server;
|
|
23
|
-
private _tlsServer;
|
|
9
|
+
export interface HttpolyglotOptions {
|
|
24
10
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
11
|
+
* Enable support for incoming TLS connections. If a TLS configuration is provided, this will
|
|
12
|
+
* be used to instantiate a TLS server to handle the connections. If a TLS server is provided,
|
|
13
|
+
* then all incoming TLS connections will be emitted as 'connection' events on the given
|
|
14
|
+
* TLS server, and all 'secureConnection' events coming from the TLS server will be handled
|
|
15
|
+
* according to the connection type detected on that socket.
|
|
27
16
|
*/
|
|
28
|
-
|
|
17
|
+
tls?: https.ServerOptions | tls.Server;
|
|
29
18
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
19
|
+
* A SOCKS server instance. This will allow incoming SOCKS connections, which will
|
|
20
|
+
* be emitted as 'connection' events on this server.
|
|
32
21
|
*/
|
|
33
|
-
|
|
22
|
+
socks?: net.Server;
|
|
34
23
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
24
|
+
* A custom handler for unknown protocols. If provided, any unrecognized protocol sockets will
|
|
25
|
+
* be emitted on this server as 'connection' events. If not provided, the default behavior is to
|
|
26
|
+
* pass the socket to the HTTP server, which will typically reject the connection as
|
|
27
|
+
* unparseable, with a 400 response.
|
|
39
28
|
*/
|
|
40
|
-
|
|
29
|
+
unknownProtocol?: net.Server;
|
|
30
|
+
}
|
|
31
|
+
declare class Server extends net.Server {
|
|
32
|
+
private _httpServer;
|
|
33
|
+
private _http2Server;
|
|
34
|
+
private _tlsServer;
|
|
35
|
+
private _socksHandler;
|
|
36
|
+
private _unknownProtocolHandler;
|
|
37
|
+
constructor(config: HttpolyglotOptions, requestListener: http.RequestListener);
|
|
41
38
|
private connectionListener;
|
|
42
39
|
private tlsListener;
|
|
43
40
|
private http2Listener;
|
|
44
41
|
}
|
|
42
|
+
export type { Server };
|
|
45
43
|
/**
|
|
46
44
|
* Create an Httpolyglot instance with just a request listener to support plain-text
|
|
47
45
|
* HTTP & HTTP/2 on the same port, with incoming TLS connections being closed immediately.
|
|
48
46
|
*/
|
|
49
47
|
export declare function createServer(requestListener: http.RequestListener): Server;
|
|
50
48
|
/**
|
|
51
|
-
* Create an
|
|
52
|
-
*
|
|
49
|
+
* Create an Httpolyglot server with advanced configuration, to enable TLS and/or SOCKS
|
|
50
|
+
* connections containing HTTP, accepting all protocols on the same port.
|
|
51
|
+
*
|
|
52
|
+
* The options can contain:
|
|
53
|
+
* - `tls`: A TLS configuration object, or an existing TLS server. If a TLS server is provided,
|
|
54
|
+
* all incoming TLS connections will be emitted as 'connection' events on the given TLS server,
|
|
55
|
+
* and all 'secureConnection' events coming from the TLS server will be handled according to
|
|
56
|
+
* the connection type (HTTP/1 or HTTP/2) detected on that socket.
|
|
57
|
+
* - `socks`: A SOCKS server instance. This will allow incoming SOCKS connections, which will
|
|
58
|
+
* be emitted as 'connection' events on this server.
|
|
53
59
|
*/
|
|
54
|
-
export declare function createServer(
|
|
55
|
-
/**
|
|
56
|
-
* Create an instance around an existing TLS server, instead of TLS configuration, to create a
|
|
57
|
-
* TLS+HTTP+HTTP/2 server with custom TLS handling. All incoming TLS requests will be emitted as
|
|
58
|
-
* 'connection' events on the given TLS server, and all 'secureConnection' events coming from the
|
|
59
|
-
* TLS server will be handled according to the connection type detected on that socket.
|
|
60
|
-
*/
|
|
61
|
-
export declare function createServer(tlsServer: tls.Server, requestListener: http.RequestListener): Server;
|
|
60
|
+
export declare function createServer(config: HttpolyglotOptions, requestListener: http.RequestListener): Server;
|
package/dist/index.js
CHANGED
|
@@ -1,59 +1,99 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createServer =
|
|
3
|
+
exports.createServer = void 0;
|
|
4
4
|
const net = require("net");
|
|
5
5
|
const tls = require("tls");
|
|
6
6
|
const http = require("http");
|
|
7
7
|
const http2 = require("http2");
|
|
8
8
|
const events_1 = require("events");
|
|
9
|
+
// 0x16 first byte is very unlikely to be a false positive as TLS is so widely used & intermediated
|
|
10
|
+
const isTls = (data) => data[0] === 0x16; // SSLv3+ or TLS handshake
|
|
11
|
+
// H2 hello is effectively guaranteed not to be a false positive, as it's very so specific, but we
|
|
12
|
+
// need to wait for the full message to confirm this.
|
|
13
|
+
const HTTP2_PREFACE = Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n');
|
|
14
|
+
const couldBeHttp2 = (data) => data[0] === HTTP2_PREFACE[0];
|
|
15
|
+
const isHttp2 = (data) => data.subarray(0, HTTP2_PREFACE.length).equals(HTTP2_PREFACE);
|
|
16
|
+
const H1_METHOD_PREFIXES = http.METHODS.map(m => m + ' ');
|
|
17
|
+
const H1_LONGEST_PREFIX = Math.max(...H1_METHOD_PREFIXES.map(m => m.length));
|
|
18
|
+
const couldBeHttp1 = (initialData) => {
|
|
19
|
+
const initialString = initialData.subarray(0, H1_LONGEST_PREFIX).toString('utf8');
|
|
20
|
+
for (let method of H1_METHOD_PREFIXES) {
|
|
21
|
+
const comparisonLength = Math.min(method.length, initialString.length);
|
|
22
|
+
if (initialString.slice(0, comparisonLength) === method.slice(0, comparisonLength)) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
};
|
|
28
|
+
// [0x4, 0x1|0x2] for SOCKS v4 seems fairly reliable. Some other protocols (Cassandra's CQL) use
|
|
29
|
+
// the first byte for versions similarly, but shouldn't conflict in practice AFAICT, and no
|
|
30
|
+
// other popular protocols actively match 0x4 that I can see.
|
|
31
|
+
const isSocksV4 = (data) => {
|
|
32
|
+
return data[0] === 0x04 && // Start of SOCKS v4 client hello
|
|
33
|
+
(data.byteLength > 1 && (data[1] === 0x1 || data[1] === 0x2)); // Any command sent is valid
|
|
34
|
+
};
|
|
35
|
+
// [0x5, len, len-bytes-of-auth-methods]. 0x5 doesn't have any visible conflicts either, and
|
|
36
|
+
// using len as a max-expected-length check should keep that fairly tight too.
|
|
37
|
+
const isSocksV5 = (data) => {
|
|
38
|
+
return data[0] === 0x05 && // Start of SOCKS v5 client hello
|
|
39
|
+
(data.byteLength > 1 && // If auth method length is present, it's non-zero and could match
|
|
40
|
+
data[1] > 0 && // (i.e. message isn't longer than it should be)
|
|
41
|
+
data.byteLength <= 2 + data[1]);
|
|
42
|
+
};
|
|
43
|
+
const isSocks = (data) => isSocksV4(data) || isSocksV5(data);
|
|
9
44
|
function onError(err) { }
|
|
10
|
-
const TLS_HANDSHAKE_BYTE = 0x16; // SSLv3+ or TLS handshake
|
|
11
|
-
const HTTP2_PREFACE = 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n';
|
|
12
|
-
const HTTP2_PREFACE_BUFFER = Buffer.from(HTTP2_PREFACE);
|
|
13
|
-
const NODE_MAJOR_VERSION = parseInt(process.version.slice(1).split('.')[0], 10);
|
|
14
45
|
class Server extends net.Server {
|
|
15
|
-
|
|
46
|
+
_httpServer;
|
|
47
|
+
_http2Server;
|
|
48
|
+
_tlsServer;
|
|
49
|
+
_socksHandler;
|
|
50
|
+
_unknownProtocolHandler;
|
|
51
|
+
constructor(configOrListener, listener) {
|
|
16
52
|
// We just act as a plain TCP server, accepting and examing
|
|
17
53
|
// each connection, then passing it to the right subserver.
|
|
18
54
|
super((socket) => this.connectionListener(socket));
|
|
19
|
-
let
|
|
20
|
-
let tlsServer;
|
|
55
|
+
let config = {};
|
|
21
56
|
let requestListener;
|
|
22
|
-
if (typeof
|
|
23
|
-
requestListener =
|
|
24
|
-
tlsConfig = undefined;
|
|
25
|
-
}
|
|
26
|
-
else if (configOrServerOrListener instanceof tls.Server) {
|
|
27
|
-
tlsServer = configOrServerOrListener;
|
|
28
|
-
requestListener = listener;
|
|
57
|
+
if (typeof configOrListener === 'function') {
|
|
58
|
+
requestListener = configOrListener;
|
|
29
59
|
}
|
|
30
60
|
else {
|
|
31
|
-
|
|
61
|
+
config = configOrListener;
|
|
32
62
|
requestListener = listener;
|
|
33
63
|
}
|
|
34
64
|
// We bind the request listener, so 'this' always refers to us, not each subserver.
|
|
35
65
|
// This means 'this' is consistent (and this.close() works).
|
|
36
|
-
|
|
66
|
+
// Use `Function.prototype.bind` directly as frameworks like Express generate
|
|
67
|
+
// methods from `http.METHODS`, and `BIND` is an included HTTP method.
|
|
68
|
+
const boundListener = Function.prototype.bind.call(requestListener, this);
|
|
37
69
|
// Create subservers for each supported protocol:
|
|
38
70
|
this._httpServer = new http.Server(boundListener);
|
|
39
71
|
this._http2Server = http2.createServer({}, boundListener);
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
72
|
+
if (config.tls) {
|
|
73
|
+
if (config.tls instanceof tls.Server) {
|
|
74
|
+
// If we've been given a preconfigured TLS server, we use that directly, and
|
|
75
|
+
// subscribe to connections there
|
|
76
|
+
this._tlsServer = config.tls;
|
|
77
|
+
this._tlsServer.on('secureConnection', this.tlsListener.bind(this));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// If we have TLS config, create a TLS server, which will pass sockets to
|
|
81
|
+
// the relevant subserver once the TLS connection is set up.
|
|
82
|
+
this._tlsServer = new tls.Server(config.tls, this.tlsListener.bind(this));
|
|
83
|
+
}
|
|
50
84
|
}
|
|
51
85
|
else {
|
|
52
|
-
// Fake server that rejects all connections:
|
|
86
|
+
// Fake TLS server that rejects all connections:
|
|
53
87
|
this._tlsServer = new events_1.EventEmitter();
|
|
54
88
|
this._tlsServer.on('connection', (socket) => socket.destroy());
|
|
55
89
|
}
|
|
90
|
+
this._socksHandler = config.socks;
|
|
91
|
+
this._unknownProtocolHandler = config.unknownProtocol;
|
|
56
92
|
const subServers = [this._httpServer, this._http2Server, this._tlsServer];
|
|
93
|
+
if (this._socksHandler)
|
|
94
|
+
subServers.push(this._socksHandler);
|
|
95
|
+
if (this._unknownProtocolHandler)
|
|
96
|
+
subServers.push(this._unknownProtocolHandler);
|
|
57
97
|
// Proxy all event listeners setup onto the subservers, so any
|
|
58
98
|
// subscriptions on this server are fed from all the subservers
|
|
59
99
|
this.on('newListener', function (eventName, listener) {
|
|
@@ -79,21 +119,28 @@ class Server extends net.Server {
|
|
|
79
119
|
else {
|
|
80
120
|
socket.removeListener('error', onError);
|
|
81
121
|
// Put the peeked data back into the socket
|
|
82
|
-
const firstByte = data[0];
|
|
83
122
|
socket.unshift(data);
|
|
84
123
|
// Pass the socket to the correct subserver:
|
|
85
|
-
if (
|
|
124
|
+
if (isTls(data)) {
|
|
86
125
|
// TLS sockets don't allow half open
|
|
87
126
|
socket.allowHalfOpen = false;
|
|
88
127
|
this._tlsServer.emit('connection', socket);
|
|
89
128
|
}
|
|
129
|
+
else if (this._socksHandler && isSocks(data)) {
|
|
130
|
+
this._socksHandler.emit('connection', socket);
|
|
131
|
+
}
|
|
90
132
|
else {
|
|
91
|
-
if (
|
|
133
|
+
if (couldBeHttp2(data)) {
|
|
92
134
|
// The connection _might_ be HTTP/2. To confirm, we need to keep
|
|
93
135
|
// reading until we get the whole stream:
|
|
94
136
|
this.http2Listener(socket);
|
|
95
137
|
}
|
|
138
|
+
else if (this._unknownProtocolHandler && !couldBeHttp1(data)) {
|
|
139
|
+
this._unknownProtocolHandler.emit('connection', socket);
|
|
140
|
+
}
|
|
96
141
|
else {
|
|
142
|
+
// We pass everything else to the HTTP server, which can handle requests
|
|
143
|
+
// and/or reject unparseable client-error connections as it sees fit.
|
|
97
144
|
this._httpServer.emit('connection', socket);
|
|
98
145
|
}
|
|
99
146
|
}
|
|
@@ -115,22 +162,15 @@ class Server extends net.Server {
|
|
|
115
162
|
const h2Server = this._http2Server;
|
|
116
163
|
const newData = socket.read() || Buffer.from([]);
|
|
117
164
|
const data = pastData ? Buffer.concat([pastData, newData]) : newData;
|
|
118
|
-
if (data.length >=
|
|
165
|
+
if (data.length >= HTTP2_PREFACE.length) {
|
|
119
166
|
socket.unshift(data);
|
|
120
|
-
if (data
|
|
167
|
+
if (isHttp2(data)) {
|
|
121
168
|
// We have a full match for the preface - it's definitely HTTP/2.
|
|
122
169
|
// For HTTP/2 we hit issues when passing non-socket streams (like H2 streams for proxying H2-over-H2).
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
// For newer node, we can fix this with a quick patch here:
|
|
130
|
-
const socketWithInternals = socket;
|
|
131
|
-
if (socketWithInternals._handle) {
|
|
132
|
-
socketWithInternals._handle.isStreamBase = false;
|
|
133
|
-
}
|
|
170
|
+
// Setting isStreamBase to false is an effective workaround for this in Node 14+
|
|
171
|
+
const socketWithInternals = socket;
|
|
172
|
+
if (socketWithInternals._handle) {
|
|
173
|
+
socketWithInternals._handle.isStreamBase = false;
|
|
134
174
|
}
|
|
135
175
|
h2Server.emit('connection', socket);
|
|
136
176
|
return;
|
|
@@ -140,7 +180,7 @@ class Server extends net.Server {
|
|
|
140
180
|
return;
|
|
141
181
|
}
|
|
142
182
|
}
|
|
143
|
-
else if (!data.equals(
|
|
183
|
+
else if (!data.equals(HTTP2_PREFACE.slice(0, data.length))) {
|
|
144
184
|
socket.unshift(data);
|
|
145
185
|
// Haven't finished the preface length, but something doesn't match already
|
|
146
186
|
h1Server.emit('connection', socket);
|
|
@@ -154,9 +194,18 @@ class Server extends net.Server {
|
|
|
154
194
|
});
|
|
155
195
|
}
|
|
156
196
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
197
|
+
function createServer(configOrListener, listener) {
|
|
198
|
+
let config;
|
|
199
|
+
let requestListener;
|
|
200
|
+
if (typeof configOrListener === 'function') {
|
|
201
|
+
config = {};
|
|
202
|
+
requestListener = configOrListener;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
config = configOrListener;
|
|
206
|
+
requestListener = listener;
|
|
207
|
+
}
|
|
208
|
+
return new Server(config, requestListener);
|
|
160
209
|
}
|
|
161
210
|
exports.createServer = createServer;
|
|
162
211
|
;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2BAA2B;AAC3B,2BAA2B;AAC3B,6BAA6B;AAE7B,+BAA+B;AAE/B,mCAAsC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@httptoolkit/httpolyglot",
|
|
3
|
-
"version": "
|
|
4
|
-
"author": "Tim Perry <
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"author": "Tim Perry <tim@httptoolkit.com>",
|
|
5
5
|
"description": "Serve http and https connections over the same port with node.js",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"test": "mocha -r ts-node/register 'test/**/*.spec.ts'"
|
|
18
18
|
},
|
|
19
19
|
"engines": {
|
|
20
|
-
"node": ">=
|
|
20
|
+
"node": ">=20.0.0"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"http",
|
|
@@ -26,12 +26,7 @@
|
|
|
26
26
|
"multiplex",
|
|
27
27
|
"polyglot"
|
|
28
28
|
],
|
|
29
|
-
"
|
|
30
|
-
{
|
|
31
|
-
"type": "MIT",
|
|
32
|
-
"url": "http://github.com/httptoolkit/httpolyglot/raw/master/LICENSE"
|
|
33
|
-
}
|
|
34
|
-
],
|
|
29
|
+
"license": "MIT",
|
|
35
30
|
"repository": {
|
|
36
31
|
"type": "git",
|
|
37
32
|
"url": "http://github.com/httptoolkit/httpolyglot.git"
|
|
@@ -45,6 +40,7 @@
|
|
|
45
40
|
"chai": "^4.3.4",
|
|
46
41
|
"mocha": "^9.1.1",
|
|
47
42
|
"rimraf": "^3.0.2",
|
|
43
|
+
"socks": "^2.8.4",
|
|
48
44
|
"ts-node": "^10.2.1",
|
|
49
45
|
"typescript": "^4.4.2"
|
|
50
46
|
}
|
package/src/index.ts
CHANGED
|
@@ -6,54 +6,88 @@ import * as http2 from 'http2';
|
|
|
6
6
|
|
|
7
7
|
import { EventEmitter } from 'events';
|
|
8
8
|
|
|
9
|
-
declare module 'net' {
|
|
10
|
-
interface Socket {
|
|
11
|
-
/**
|
|
12
|
-
* Only preserved for types backward compat - always undefined in new releases.
|
|
13
|
-
*
|
|
14
|
-
* @deprecated
|
|
15
|
-
*/
|
|
16
|
-
__httpPeekedData?: Buffer;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
9
|
|
|
20
|
-
|
|
10
|
+
// 0x16 first byte is very unlikely to be a false positive as TLS is so widely used & intermediated
|
|
11
|
+
const isTls = (data: Buffer) => data[0] === 0x16; // SSLv3+ or TLS handshake
|
|
21
12
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
13
|
+
// H2 hello is effectively guaranteed not to be a false positive, as it's very so specific, but we
|
|
14
|
+
// need to wait for the full message to confirm this.
|
|
15
|
+
const HTTP2_PREFACE = Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n');
|
|
16
|
+
const couldBeHttp2 = (data: Buffer) => data[0] === HTTP2_PREFACE[0];
|
|
17
|
+
const isHttp2 = (data: Buffer) => data.subarray(0, HTTP2_PREFACE.length).equals(HTTP2_PREFACE);
|
|
25
18
|
|
|
26
|
-
const
|
|
19
|
+
const H1_METHOD_PREFIXES = http.METHODS.map(m => m + ' ');
|
|
20
|
+
const H1_LONGEST_PREFIX = Math.max(...H1_METHOD_PREFIXES.map(m => m.length));
|
|
21
|
+
const couldBeHttp1 = (initialData: Buffer) => {
|
|
22
|
+
const initialString = initialData.subarray(0, H1_LONGEST_PREFIX).toString('utf8');
|
|
23
|
+
for (let method of H1_METHOD_PREFIXES) {
|
|
24
|
+
const comparisonLength = Math.min(method.length, initialString.length);
|
|
25
|
+
if (initialString.slice(0, comparisonLength) === method.slice(0, comparisonLength)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
};
|
|
27
31
|
|
|
28
|
-
|
|
32
|
+
// [0x4, 0x1|0x2] for SOCKS v4 seems fairly reliable. Some other protocols (Cassandra's CQL) use
|
|
33
|
+
// the first byte for versions similarly, but shouldn't conflict in practice AFAICT, and no
|
|
34
|
+
// other popular protocols actively match 0x4 that I can see.
|
|
35
|
+
const isSocksV4 = (data: Buffer) => {
|
|
36
|
+
return data[0] === 0x04 && // Start of SOCKS v4 client hello
|
|
37
|
+
(data.byteLength > 1 && (data[1] === 0x1 || data[1] === 0x2)); // Any command sent is valid
|
|
38
|
+
}
|
|
39
|
+
// [0x5, len, len-bytes-of-auth-methods]. 0x5 doesn't have any visible conflicts either, and
|
|
40
|
+
// using len as a max-expected-length check should keep that fairly tight too.
|
|
41
|
+
const isSocksV5 = (data: Buffer) => {
|
|
42
|
+
return data[0] === 0x05 && // Start of SOCKS v5 client hello
|
|
43
|
+
(data.byteLength > 1 && // If auth method length is present, it's non-zero and could match
|
|
44
|
+
data[1] > 0 && // (i.e. message isn't longer than it should be)
|
|
45
|
+
data.byteLength <= 2 + data[1]
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const isSocks = (data: Buffer) => isSocksV4(data) || isSocksV5(data);
|
|
29
49
|
|
|
30
|
-
|
|
50
|
+
function onError(err: any) {}
|
|
31
51
|
|
|
32
|
-
|
|
33
|
-
private _http2Server: http2.Http2Server;
|
|
34
|
-
private _tlsServer: EventEmitter;
|
|
52
|
+
type Http2Listener = (request: http2.Http2ServerRequest, response: http2.Http2ServerResponse) => void;
|
|
35
53
|
|
|
54
|
+
export interface HttpolyglotOptions {
|
|
36
55
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
56
|
+
* Enable support for incoming TLS connections. If a TLS configuration is provided, this will
|
|
57
|
+
* be used to instantiate a TLS server to handle the connections. If a TLS server is provided,
|
|
58
|
+
* then all incoming TLS connections will be emitted as 'connection' events on the given
|
|
59
|
+
* TLS server, and all 'secureConnection' events coming from the TLS server will be handled
|
|
60
|
+
* according to the connection type detected on that socket.
|
|
39
61
|
*/
|
|
40
|
-
|
|
62
|
+
tls?: https.ServerOptions | tls.Server;
|
|
63
|
+
|
|
41
64
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
65
|
+
* A SOCKS server instance. This will allow incoming SOCKS connections, which will
|
|
66
|
+
* be emitted as 'connection' events on this server.
|
|
44
67
|
*/
|
|
45
|
-
|
|
68
|
+
socks?: net.Server;
|
|
69
|
+
|
|
46
70
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
71
|
+
* A custom handler for unknown protocols. If provided, any unrecognized protocol sockets will
|
|
72
|
+
* be emitted on this server as 'connection' events. If not provided, the default behavior is to
|
|
73
|
+
* pass the socket to the HTTP server, which will typically reject the connection as
|
|
74
|
+
* unparseable, with a 400 response.
|
|
51
75
|
*/
|
|
52
|
-
|
|
76
|
+
unknownProtocol?: net.Server;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
class Server extends net.Server {
|
|
80
|
+
|
|
81
|
+
private _httpServer: http.Server;
|
|
82
|
+
private _http2Server: http2.Http2Server;
|
|
83
|
+
private _tlsServer: EventEmitter;
|
|
84
|
+
private _socksHandler: EventEmitter | undefined;
|
|
85
|
+
private _unknownProtocolHandler: EventEmitter | undefined;
|
|
86
|
+
|
|
87
|
+
constructor(config: HttpolyglotOptions, requestListener: http.RequestListener);
|
|
53
88
|
constructor(
|
|
54
|
-
|
|
55
|
-
|
|
|
56
|
-
| tls.Server
|
|
89
|
+
configOrListener:
|
|
90
|
+
| HttpolyglotOptions
|
|
57
91
|
| http.RequestListener,
|
|
58
92
|
listener?: http.RequestListener
|
|
59
93
|
) {
|
|
@@ -61,45 +95,49 @@ export class Server extends net.Server {
|
|
|
61
95
|
// each connection, then passing it to the right subserver.
|
|
62
96
|
super((socket) => this.connectionListener(socket));
|
|
63
97
|
|
|
64
|
-
let
|
|
65
|
-
let tlsServer: tls.Server | undefined;
|
|
98
|
+
let config: HttpolyglotOptions = {};
|
|
66
99
|
let requestListener: http.RequestListener;
|
|
67
100
|
|
|
68
|
-
if (typeof
|
|
69
|
-
requestListener =
|
|
70
|
-
tlsConfig = undefined;
|
|
71
|
-
} else if (configOrServerOrListener instanceof tls.Server) {
|
|
72
|
-
tlsServer = configOrServerOrListener;
|
|
73
|
-
requestListener = listener!;
|
|
101
|
+
if (typeof configOrListener === 'function') {
|
|
102
|
+
requestListener = configOrListener;
|
|
74
103
|
} else {
|
|
75
|
-
|
|
104
|
+
config = configOrListener;
|
|
76
105
|
requestListener = listener!;
|
|
77
106
|
}
|
|
78
107
|
|
|
79
108
|
// We bind the request listener, so 'this' always refers to us, not each subserver.
|
|
80
109
|
// This means 'this' is consistent (and this.close() works).
|
|
81
|
-
|
|
110
|
+
// Use `Function.prototype.bind` directly as frameworks like Express generate
|
|
111
|
+
// methods from `http.METHODS`, and `BIND` is an included HTTP method.
|
|
112
|
+
const boundListener = Function.prototype.bind.call(requestListener, this);
|
|
82
113
|
|
|
83
114
|
// Create subservers for each supported protocol:
|
|
84
115
|
this._httpServer = new http.Server(boundListener);
|
|
85
116
|
this._http2Server = http2.createServer({}, boundListener as any as Http2Listener);
|
|
86
117
|
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
118
|
+
if (config.tls) {
|
|
119
|
+
if (config.tls instanceof tls.Server) {
|
|
120
|
+
// If we've been given a preconfigured TLS server, we use that directly, and
|
|
121
|
+
// subscribe to connections there
|
|
122
|
+
this._tlsServer = config.tls;
|
|
123
|
+
this._tlsServer.on('secureConnection', this.tlsListener.bind(this));
|
|
124
|
+
} else {
|
|
125
|
+
// If we have TLS config, create a TLS server, which will pass sockets to
|
|
126
|
+
// the relevant subserver once the TLS connection is set up.
|
|
127
|
+
this._tlsServer = new tls.Server(config.tls, this.tlsListener.bind(this));
|
|
128
|
+
}
|
|
96
129
|
} else {
|
|
97
|
-
// Fake server that rejects all connections:
|
|
130
|
+
// Fake TLS server that rejects all connections:
|
|
98
131
|
this._tlsServer = new EventEmitter();
|
|
99
132
|
this._tlsServer.on('connection', (socket) => socket.destroy());
|
|
100
133
|
}
|
|
101
134
|
|
|
135
|
+
this._socksHandler = config.socks;
|
|
136
|
+
this._unknownProtocolHandler = config.unknownProtocol;
|
|
137
|
+
|
|
102
138
|
const subServers = [this._httpServer, this._http2Server, this._tlsServer];
|
|
139
|
+
if (this._socksHandler) subServers.push(this._socksHandler);
|
|
140
|
+
if (this._unknownProtocolHandler) subServers.push(this._unknownProtocolHandler);
|
|
103
141
|
|
|
104
142
|
// Proxy all event listeners setup onto the subservers, so any
|
|
105
143
|
// subscriptions on this server are fed from all the subservers
|
|
@@ -130,20 +168,25 @@ export class Server extends net.Server {
|
|
|
130
168
|
socket.removeListener('error', onError);
|
|
131
169
|
|
|
132
170
|
// Put the peeked data back into the socket
|
|
133
|
-
const firstByte = data[0];
|
|
134
171
|
socket.unshift(data);
|
|
135
172
|
|
|
136
173
|
// Pass the socket to the correct subserver:
|
|
137
|
-
if (
|
|
174
|
+
if (isTls(data)) {
|
|
138
175
|
// TLS sockets don't allow half open
|
|
139
176
|
socket.allowHalfOpen = false;
|
|
140
177
|
this._tlsServer.emit('connection', socket);
|
|
178
|
+
} else if (this._socksHandler && isSocks(data)) {
|
|
179
|
+
this._socksHandler.emit('connection', socket);
|
|
141
180
|
} else {
|
|
142
|
-
if (
|
|
181
|
+
if (couldBeHttp2(data)) {
|
|
143
182
|
// The connection _might_ be HTTP/2. To confirm, we need to keep
|
|
144
183
|
// reading until we get the whole stream:
|
|
145
184
|
this.http2Listener(socket);
|
|
185
|
+
} else if (this._unknownProtocolHandler && !couldBeHttp1(data)) {
|
|
186
|
+
this._unknownProtocolHandler.emit('connection', socket);
|
|
146
187
|
} else {
|
|
188
|
+
// We pass everything else to the HTTP server, which can handle requests
|
|
189
|
+
// and/or reject unparseable client-error connections as it sees fit.
|
|
147
190
|
this._httpServer.emit('connection', socket);
|
|
148
191
|
}
|
|
149
192
|
}
|
|
@@ -169,22 +212,16 @@ export class Server extends net.Server {
|
|
|
169
212
|
const newData: Buffer = socket.read() || Buffer.from([]);
|
|
170
213
|
const data = pastData ? Buffer.concat([pastData, newData]) : newData;
|
|
171
214
|
|
|
172
|
-
if (data.length >=
|
|
215
|
+
if (data.length >= HTTP2_PREFACE.length) {
|
|
173
216
|
socket.unshift(data);
|
|
174
|
-
if (data
|
|
217
|
+
if (isHttp2(data)) {
|
|
175
218
|
// We have a full match for the preface - it's definitely HTTP/2.
|
|
176
219
|
|
|
177
220
|
// For HTTP/2 we hit issues when passing non-socket streams (like H2 streams for proxying H2-over-H2).
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
} else {
|
|
183
|
-
// For newer node, we can fix this with a quick patch here:
|
|
184
|
-
const socketWithInternals = socket as { _handle?: { isStreamBase?: boolean } };
|
|
185
|
-
if (socketWithInternals._handle) {
|
|
186
|
-
socketWithInternals._handle.isStreamBase = false;
|
|
187
|
-
}
|
|
221
|
+
// Setting isStreamBase to false is an effective workaround for this in Node 14+
|
|
222
|
+
const socketWithInternals = socket as { _handle?: { isStreamBase?: boolean } };
|
|
223
|
+
if (socketWithInternals._handle) {
|
|
224
|
+
socketWithInternals._handle.isStreamBase = false;
|
|
188
225
|
}
|
|
189
226
|
|
|
190
227
|
h2Server.emit('connection', socket);
|
|
@@ -193,7 +230,7 @@ export class Server extends net.Server {
|
|
|
193
230
|
h1Server.emit('connection', socket);
|
|
194
231
|
return;
|
|
195
232
|
}
|
|
196
|
-
} else if (!data.equals(
|
|
233
|
+
} else if (!data.equals(HTTP2_PREFACE.slice(0, data.length))) {
|
|
197
234
|
socket.unshift(data);
|
|
198
235
|
// Haven't finished the preface length, but something doesn't match already
|
|
199
236
|
h1Server.emit('connection', socket);
|
|
@@ -209,28 +246,41 @@ export class Server extends net.Server {
|
|
|
209
246
|
}
|
|
210
247
|
}
|
|
211
248
|
|
|
249
|
+
export type { Server };
|
|
250
|
+
|
|
212
251
|
/**
|
|
213
252
|
* Create an Httpolyglot instance with just a request listener to support plain-text
|
|
214
253
|
* HTTP & HTTP/2 on the same port, with incoming TLS connections being closed immediately.
|
|
215
254
|
*/
|
|
216
255
|
export function createServer(requestListener: http.RequestListener): Server;
|
|
217
256
|
/**
|
|
218
|
-
* Create an
|
|
219
|
-
*
|
|
257
|
+
* Create an Httpolyglot server with advanced configuration, to enable TLS and/or SOCKS
|
|
258
|
+
* connections containing HTTP, accepting all protocols on the same port.
|
|
259
|
+
*
|
|
260
|
+
* The options can contain:
|
|
261
|
+
* - `tls`: A TLS configuration object, or an existing TLS server. If a TLS server is provided,
|
|
262
|
+
* all incoming TLS connections will be emitted as 'connection' events on the given TLS server,
|
|
263
|
+
* and all 'secureConnection' events coming from the TLS server will be handled according to
|
|
264
|
+
* the connection type (HTTP/1 or HTTP/2) detected on that socket.
|
|
265
|
+
* - `socks`: A SOCKS server instance. This will allow incoming SOCKS connections, which will
|
|
266
|
+
* be emitted as 'connection' events on this server.
|
|
220
267
|
*/
|
|
221
|
-
export function createServer(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
* 'connection' events on the given TLS server, and all 'secureConnection' events coming from the
|
|
226
|
-
* TLS server will be handled according to the connection type detected on that socket.
|
|
227
|
-
*/
|
|
228
|
-
export function createServer(tlsServer: tls.Server, requestListener: http.RequestListener): Server;
|
|
229
|
-
export function createServer(configOrServerOrListener:
|
|
230
|
-
| https.ServerOptions
|
|
231
|
-
| http.RequestListener
|
|
232
|
-
| tls.Server,
|
|
268
|
+
export function createServer(config: HttpolyglotOptions, requestListener: http.RequestListener): Server;
|
|
269
|
+
export function createServer(configOrListener:
|
|
270
|
+
| HttpolyglotOptions
|
|
271
|
+
| http.RequestListener,
|
|
233
272
|
listener?: http.RequestListener
|
|
234
273
|
) {
|
|
235
|
-
|
|
274
|
+
let config: HttpolyglotOptions;
|
|
275
|
+
let requestListener: http.RequestListener;
|
|
276
|
+
|
|
277
|
+
if (typeof configOrListener === 'function') {
|
|
278
|
+
config = {}
|
|
279
|
+
requestListener = configOrListener;
|
|
280
|
+
} else {
|
|
281
|
+
config = configOrListener;
|
|
282
|
+
requestListener = listener!;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return new Server(config, requestListener);
|
|
236
286
|
};
|