@httptoolkit/httpolyglot 0.2.0 → 2.0.1

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/README.md CHANGED
@@ -1,18 +1,21 @@
1
- Description
2
- ===========
1
+ # Httpolyglot [![Build Status](https://github.com/httptoolkit/httpolyglot/workflows/CI/badge.svg)](https://github.com/httptoolkit/httpolyglot/actions) [![Available on NPM](https://img.shields.io/npm/v/@httptoolkit/httpolyglot.svg)](https://npmjs.com/package/@httptoolkit/httpolyglot)
2
+
3
+ > _Part of [HTTP Toolkit](https://httptoolkit.tech): powerful tools for building, testing & debugging HTTP(S)_
3
4
 
4
5
  A module for serving http and https connections over the same port.
5
6
 
6
7
  Forked from the original [`httpolyglot`](https://github.com/mscdex/httpolyglot) to fix various issues required for [HTTP Toolkit](https://httptoolkit.tech), including:
7
8
 
9
+ * Support for HTTP/2
8
10
  * Fixing `tlsClientError`: https://github.com/mscdex/httpolyglot/pull/11.
9
11
  * Exposing the lost bytes from https://github.com/mscdex/httpolyglot/issues/13 on the socket, as `__httpPeekedData`.
10
12
  * Dropping support for old versions of Node (and thereby simplifying the code somewhat)
13
+ * Converting to TypeScript
11
14
 
12
15
  Requirements
13
16
  ============
14
17
 
15
- * [node.js](http://nodejs.org/) -- v8.0.0 or newer
18
+ * [node.js](http://nodejs.org/) -- v12.0.0 or newer
16
19
 
17
20
 
18
21
  Install
@@ -0,0 +1,20 @@
1
+ /// <reference types="node" />
2
+ import * as net from 'net';
3
+ import * as http from 'http';
4
+ import * as https from 'https';
5
+ declare module 'net' {
6
+ interface Socket {
7
+ __httpPeekedData?: Buffer;
8
+ }
9
+ }
10
+ export declare class Server extends net.Server {
11
+ private _httpServer;
12
+ private _http2Server;
13
+ private _tlsServer;
14
+ constructor(requestListener: http.RequestListener);
15
+ constructor(tlsConfig: https.ServerOptions, requestListener: http.RequestListener);
16
+ private connectionListener;
17
+ private http2Listener;
18
+ }
19
+ export declare function createServer(requestListener: http.RequestListener): Server;
20
+ export declare function createServer(tlsConfig: https.ServerOptions, requestListener: http.RequestListener): Server;
package/dist/index.js ADDED
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createServer = exports.Server = void 0;
4
+ const net = require("net");
5
+ const tls = require("tls");
6
+ const http = require("http");
7
+ const http2 = require("http2");
8
+ const events_1 = require("events");
9
+ 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
+ class Server extends net.Server {
15
+ constructor(configOrListener, listener) {
16
+ // We just act as a plain TCP server, accepting and examing
17
+ // each connection, then passing it to the right subserver.
18
+ super((socket) => this.connectionListener(socket));
19
+ let tlsConfig;
20
+ let requestListener;
21
+ if (typeof configOrListener === 'function') {
22
+ requestListener = configOrListener;
23
+ tlsConfig = undefined;
24
+ }
25
+ else {
26
+ tlsConfig = configOrListener;
27
+ requestListener = listener;
28
+ }
29
+ // We bind the request listener, so 'this' always refers to us, not each subserver.
30
+ // This means 'this' is consistent (and this.close() works).
31
+ const boundListener = requestListener.bind(this);
32
+ // Create subservers for each supported protocol:
33
+ this._httpServer = new http.Server(boundListener);
34
+ this._http2Server = http2.createServer({}, boundListener);
35
+ if (typeof tlsConfig === 'object') {
36
+ // If we have TLS config, create a TLS server, which will pass sockets to
37
+ // the relevant subserver once the TLS connection is set up.
38
+ this._tlsServer = new tls.Server(tlsConfig, (tlsSocket) => {
39
+ if (tlsSocket.alpnProtocol === false || tlsSocket.alpnProtocol === 'http/1.1') {
40
+ this._httpServer.emit('connection', tlsSocket);
41
+ }
42
+ else {
43
+ this._http2Server.emit('connection', tlsSocket);
44
+ }
45
+ });
46
+ }
47
+ else {
48
+ // Fake server that rejects all connections:
49
+ this._tlsServer = new events_1.EventEmitter();
50
+ this._tlsServer.on('connection', (socket) => socket.destroy());
51
+ }
52
+ const subServers = [this._httpServer, this._http2Server, this._tlsServer];
53
+ // Proxy all event listeners setup onto the subservers, so any
54
+ // subscriptions on this server are fed from all the subservers
55
+ this.on('newListener', function (eventName, listener) {
56
+ subServers.forEach(function (subServer) {
57
+ subServer.addListener(eventName, listener);
58
+ });
59
+ });
60
+ this.on('removeListener', function (eventName, listener) {
61
+ subServers.forEach(function (subServer) {
62
+ subServer.removeListener(eventName, listener);
63
+ });
64
+ });
65
+ }
66
+ connectionListener(socket) {
67
+ const data = socket.read(1);
68
+ if (data === null) {
69
+ socket.removeListener('error', onError);
70
+ socket.on('error', onError);
71
+ socket.once('readable', () => {
72
+ this.connectionListener(socket);
73
+ });
74
+ }
75
+ else {
76
+ socket.removeListener('error', onError);
77
+ // Put the peeked data back into the socket
78
+ const firstByte = data[0];
79
+ socket.unshift(data);
80
+ // Pass the socket to the correct subserver:
81
+ if (firstByte === TLS_HANDSHAKE_BYTE) {
82
+ // TLS sockets don't allow half open
83
+ socket.allowHalfOpen = false;
84
+ this._tlsServer.emit('connection', socket);
85
+ }
86
+ else {
87
+ if (firstByte === HTTP2_PREFACE_BUFFER[0]) {
88
+ // The connection _might_ be HTTP/2. To confirm, we need to keep
89
+ // reading until we get the whole stream:
90
+ this.http2Listener(socket);
91
+ }
92
+ else {
93
+ // The above unshift isn't always sufficient to invisibly replace the
94
+ // read data. The rawPacket property on errors in the clientError event
95
+ // for plain HTTP servers loses this data - this prop makes it available.
96
+ // Bit of a hacky fix, but sufficient to allow for manual workarounds.
97
+ socket.__httpPeekedData = data;
98
+ this._httpServer.emit('connection', socket);
99
+ }
100
+ }
101
+ }
102
+ }
103
+ http2Listener(socket, pastData) {
104
+ const h1Server = this._httpServer;
105
+ const h2Server = this._http2Server;
106
+ const newData = socket.read() || Buffer.from([]);
107
+ const data = pastData ? Buffer.concat([pastData, newData]) : newData;
108
+ if (data.length >= HTTP2_PREFACE_BUFFER.length) {
109
+ socket.unshift(data);
110
+ if (data.slice(0, HTTP2_PREFACE_BUFFER.length).equals(HTTP2_PREFACE_BUFFER)) {
111
+ // We have a full match for the preface - it's definitely HTTP/2.
112
+ // For HTTP/2 we hit issues when passing non-socket streams (like H2 streams for proxying H2-over-H2).
113
+ if (NODE_MAJOR_VERSION <= 12) {
114
+ // For Node 12 and older, we need a (later deprecated) stream wrapper:
115
+ const StreamWrapper = require('_stream_wrap');
116
+ socket = new StreamWrapper(socket);
117
+ }
118
+ else {
119
+ // For newer node, we can fix this with a quick patch here:
120
+ const socketWithInternals = socket;
121
+ if (socketWithInternals._handle) {
122
+ socketWithInternals._handle.isStreamBase = false;
123
+ }
124
+ }
125
+ h2Server.emit('connection', socket);
126
+ return;
127
+ }
128
+ else {
129
+ h1Server.emit('connection', socket);
130
+ return;
131
+ }
132
+ }
133
+ else if (!data.equals(HTTP2_PREFACE_BUFFER.slice(0, data.length))) {
134
+ socket.unshift(data);
135
+ // Haven't finished the preface length, but something doesn't match already
136
+ h1Server.emit('connection', socket);
137
+ return;
138
+ }
139
+ // Not enough data to know either way - try again, waiting for more:
140
+ socket.removeListener('error', onError);
141
+ socket.on('error', onError);
142
+ socket.once('readable', () => {
143
+ this.http2Listener.call(this, socket, data);
144
+ });
145
+ }
146
+ }
147
+ exports.Server = Server;
148
+ function createServer(configOrListener, listener) {
149
+ return new Server(configOrListener, listener);
150
+ }
151
+ exports.createServer = createServer;
152
+ ;
153
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,18 +1,28 @@
1
1
  {
2
2
  "name": "@httptoolkit/httpolyglot",
3
- "version": "0.2.0",
3
+ "version": "2.0.1",
4
4
  "author": "Tim Perry <pimterry@gmail.com>",
5
5
  "description": "Serve http and https connections over the same port with node.js",
6
- "main": "./lib/index.js",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist/",
10
+ "src/"
11
+ ],
7
12
  "scripts": {
8
- "test": "node test/test.js"
13
+ "prebuild": "rimraf dist/*",
14
+ "build": "tsc",
15
+ "prepack": "npm run build",
16
+ "pretest": "npm run build",
17
+ "test": "mocha -r ts-node/register 'test/**/*.spec.ts'"
9
18
  },
10
19
  "engines": {
11
- "node": ">=8.0.0"
20
+ "node": ">=12.0.0"
12
21
  },
13
22
  "keywords": [
14
23
  "http",
15
24
  "https",
25
+ "http2",
16
26
  "multiplex",
17
27
  "polyglot"
18
28
  ],
@@ -25,5 +35,17 @@
25
35
  "repository": {
26
36
  "type": "git",
27
37
  "url": "http://github.com/httptoolkit/httpolyglot.git"
38
+ },
39
+ "dependencies": {
40
+ "@types/node": "^16.7.10"
41
+ },
42
+ "devDependencies": {
43
+ "@types/chai": "^4.2.21",
44
+ "@types/mocha": "^9.0.0",
45
+ "chai": "^4.3.4",
46
+ "mocha": "^9.1.1",
47
+ "rimraf": "^3.0.2",
48
+ "ts-node": "^10.2.1",
49
+ "typescript": "^4.4.2"
28
50
  }
29
51
  }
package/src/index.ts ADDED
@@ -0,0 +1,180 @@
1
+ import * as net from 'net';
2
+ import * as tls from 'tls';
3
+ import * as http from 'http';
4
+ import * as https from 'https';
5
+ import * as http2 from 'http2';
6
+
7
+ import { EventEmitter } from 'events';
8
+
9
+ declare module 'net' {
10
+ interface Socket {
11
+ __httpPeekedData?: Buffer;
12
+ }
13
+ }
14
+
15
+ function onError(err: any) {}
16
+
17
+ const TLS_HANDSHAKE_BYTE = 0x16; // SSLv3+ or TLS handshake
18
+ const HTTP2_PREFACE = 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n';
19
+ const HTTP2_PREFACE_BUFFER = Buffer.from(HTTP2_PREFACE);
20
+
21
+ const NODE_MAJOR_VERSION = parseInt(process.version.slice(1).split('.')[0], 10);
22
+
23
+ type Http2Listener = (request: http2.Http2ServerRequest, response: http2.Http2ServerResponse) => void;
24
+
25
+ export class Server extends net.Server {
26
+
27
+ private _httpServer: http.Server;
28
+ private _http2Server: http2.Http2Server;
29
+ private _tlsServer: EventEmitter;
30
+
31
+ constructor(requestListener: http.RequestListener);
32
+ constructor(tlsConfig: https.ServerOptions, requestListener: http.RequestListener);
33
+ constructor(configOrListener: https.ServerOptions | http.RequestListener, listener?: http.RequestListener) {
34
+ // We just act as a plain TCP server, accepting and examing
35
+ // each connection, then passing it to the right subserver.
36
+ super((socket) => this.connectionListener(socket));
37
+
38
+ let tlsConfig: https.ServerOptions | undefined;
39
+ let requestListener: http.RequestListener;
40
+
41
+ if (typeof configOrListener === 'function') {
42
+ requestListener = configOrListener;
43
+ tlsConfig = undefined;
44
+ } else {
45
+ tlsConfig = configOrListener;
46
+ requestListener = listener!;
47
+ }
48
+
49
+ // We bind the request listener, so 'this' always refers to us, not each subserver.
50
+ // This means 'this' is consistent (and this.close() works).
51
+ const boundListener = requestListener.bind(this);
52
+
53
+ // Create subservers for each supported protocol:
54
+ this._httpServer = new http.Server(boundListener);
55
+ this._http2Server = http2.createServer({}, boundListener as any as Http2Listener);
56
+
57
+ if (typeof tlsConfig === 'object') {
58
+ // If we have TLS config, create a TLS server, which will pass sockets to
59
+ // the relevant subserver once the TLS connection is set up.
60
+ this._tlsServer = new tls.Server(tlsConfig, (tlsSocket) => {
61
+ if (tlsSocket.alpnProtocol === false || tlsSocket.alpnProtocol === 'http/1.1') {
62
+ this._httpServer.emit('connection', tlsSocket);
63
+ } else {
64
+ this._http2Server.emit('connection', tlsSocket);
65
+ }
66
+ });
67
+ } else {
68
+ // Fake server that rejects all connections:
69
+ this._tlsServer = new EventEmitter();
70
+ this._tlsServer.on('connection', (socket) => socket.destroy());
71
+ }
72
+
73
+ const subServers = [this._httpServer, this._http2Server, this._tlsServer];
74
+
75
+ // Proxy all event listeners setup onto the subservers, so any
76
+ // subscriptions on this server are fed from all the subservers
77
+ this.on('newListener', function (eventName, listener) {
78
+ subServers.forEach(function (subServer) {
79
+ subServer.addListener(eventName, listener);
80
+ })
81
+ });
82
+
83
+ this.on('removeListener', function (eventName, listener) {
84
+ subServers.forEach(function (subServer) {
85
+ subServer.removeListener(eventName, listener);
86
+ })
87
+ });
88
+ }
89
+
90
+ private connectionListener(socket: net.Socket) {
91
+ const data = socket.read(1);
92
+
93
+ if (data === null) {
94
+ socket.removeListener('error', onError);
95
+ socket.on('error', onError);
96
+
97
+ socket.once('readable', () => {
98
+ this.connectionListener(socket);
99
+ });
100
+ } else {
101
+ socket.removeListener('error', onError);
102
+
103
+ // Put the peeked data back into the socket
104
+ const firstByte = data[0];
105
+ socket.unshift(data);
106
+
107
+ // Pass the socket to the correct subserver:
108
+ if (firstByte === TLS_HANDSHAKE_BYTE) {
109
+ // TLS sockets don't allow half open
110
+ socket.allowHalfOpen = false;
111
+ this._tlsServer.emit('connection', socket);
112
+ } else {
113
+ if (firstByte === HTTP2_PREFACE_BUFFER[0]) {
114
+ // The connection _might_ be HTTP/2. To confirm, we need to keep
115
+ // reading until we get the whole stream:
116
+ this.http2Listener(socket);
117
+ } else {
118
+ // The above unshift isn't always sufficient to invisibly replace the
119
+ // read data. The rawPacket property on errors in the clientError event
120
+ // for plain HTTP servers loses this data - this prop makes it available.
121
+ // Bit of a hacky fix, but sufficient to allow for manual workarounds.
122
+ socket.__httpPeekedData = data;
123
+ this._httpServer.emit('connection', socket);
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ private http2Listener(socket: net.Socket, pastData?: Buffer) {
130
+ const h1Server = this._httpServer;
131
+ const h2Server = this._http2Server;
132
+
133
+ const newData: Buffer = socket.read() || Buffer.from([]);
134
+ const data = pastData ? Buffer.concat([pastData, newData]) : newData;
135
+
136
+ if (data.length >= HTTP2_PREFACE_BUFFER.length) {
137
+ socket.unshift(data);
138
+ if (data.slice(0, HTTP2_PREFACE_BUFFER.length).equals(HTTP2_PREFACE_BUFFER)) {
139
+ // We have a full match for the preface - it's definitely HTTP/2.
140
+
141
+ // For HTTP/2 we hit issues when passing non-socket streams (like H2 streams for proxying H2-over-H2).
142
+ if (NODE_MAJOR_VERSION <= 12) {
143
+ // For Node 12 and older, we need a (later deprecated) stream wrapper:
144
+ const StreamWrapper = require('_stream_wrap');
145
+ socket = new StreamWrapper(socket);
146
+ } else {
147
+ // For newer node, we can fix this with a quick patch here:
148
+ const socketWithInternals = socket as { _handle?: { isStreamBase?: boolean } };
149
+ if (socketWithInternals._handle) {
150
+ socketWithInternals._handle.isStreamBase = false;
151
+ }
152
+ }
153
+
154
+ h2Server.emit('connection', socket);
155
+ return;
156
+ } else {
157
+ h1Server.emit('connection', socket);
158
+ return;
159
+ }
160
+ } else if (!data.equals(HTTP2_PREFACE_BUFFER.slice(0, data.length))) {
161
+ socket.unshift(data);
162
+ // Haven't finished the preface length, but something doesn't match already
163
+ h1Server.emit('connection', socket);
164
+ return;
165
+ }
166
+
167
+ // Not enough data to know either way - try again, waiting for more:
168
+ socket.removeListener('error', onError);
169
+ socket.on('error', onError);
170
+ socket.once('readable', () => {
171
+ this.http2Listener.call(this, socket, data);
172
+ });
173
+ }
174
+ }
175
+
176
+ export function createServer(requestListener: http.RequestListener): Server;
177
+ export function createServer(tlsConfig: https.ServerOptions, requestListener: http.RequestListener): Server;
178
+ export function createServer(configOrListener: https.ServerOptions | http.RequestListener, listener?: http.RequestListener) {
179
+ return new Server(configOrListener as any, listener as any);
180
+ };
package/lib/index.js DELETED
@@ -1,87 +0,0 @@
1
- const http = require('http');
2
- const https = require('https');
3
- const inherits = require('util').inherits;
4
- const httpSocketHandler = http._connectionListener;
5
-
6
- function Server(tlsconfig, requestListener) {
7
- if (!(this instanceof Server)) return new Server(tlsconfig, requestListener);
8
-
9
- if (typeof tlsconfig === 'function') {
10
- requestListener = tlsconfig;
11
- tlsconfig = undefined;
12
- }
13
-
14
- if (typeof tlsconfig === 'object') {
15
- this.removeAllListeners('connection');
16
-
17
- https.Server.call(this, tlsconfig, requestListener);
18
-
19
- // capture https socket handler, it's not exported like http's socket
20
- // handler
21
- const connev = this._events.connection;
22
- if (typeof connev === 'function') {
23
- this._tlsHandler = connev;
24
- } else {
25
- this._tlsHandler = connev[connev.length - 1];
26
- }
27
- this.removeListener('connection', this._tlsHandler);
28
-
29
- this._connListener = connectionListener;
30
- this.on('connection', connectionListener);
31
-
32
- // copy from http.Server
33
- this.timeout = 2 * 60 * 1000;
34
- this.allowHalfOpen = true;
35
- this.httpAllowHalfOpen = false;
36
- } else {
37
- http.Server.call(this, requestListener);
38
- }
39
- }
40
- inherits(Server, https.Server);
41
-
42
- Server.prototype.setTimeout = function (msecs, callback) {
43
- this.timeout = msecs;
44
- if (callback) this.on('timeout', callback);
45
- };
46
-
47
- Server.prototype.__httpSocketHandler = httpSocketHandler;
48
-
49
- function onError(err) {}
50
-
51
- const connectionListener = function (socket) {
52
- const data = socket.read(1);
53
-
54
- if (data === null) {
55
- socket.removeListener('error', onError);
56
- socket.on('error', onError);
57
-
58
- socket.once('readable', () => {
59
- this._connListener(socket);
60
- });
61
- } else {
62
- socket.removeListener('error', onError);
63
-
64
- const firstByte = data[0];
65
- socket.unshift(data);
66
- if (firstByte < 32 || firstByte >= 127) {
67
- // tls/ssl
68
- // TLS sockets don't allow half open
69
- socket.allowHalfOpen = false;
70
- this._tlsHandler(socket);
71
- } else {
72
- // The above unshift isn't always sufficient to invisibly replace the
73
- // read data. The rawPacket property on errors in the clientError event
74
- // specifically is missing this data - this prop makes it available.
75
- // Bit of a hacky fix, but sufficient to allow for manual workarounds.
76
- socket.__httpPeekedData = data;
77
-
78
- this.__httpSocketHandler(socket);
79
- }
80
- }
81
- };
82
-
83
- exports.Server = Server;
84
-
85
- exports.createServer = function (tlsconfig, requestListener) {
86
- return new Server(tlsconfig, requestListener);
87
- };
package/q DELETED
@@ -1,22 +0,0 @@
1
- *-. 1d3593f (refs/stash) WIP on master: 2ddda12 Update docs & details to fork the package for httptoolkit use
2
- |\ \
3
- | | * cd20755 untracked files on master: 2ddda12 Update docs & details to fork the package for httptoolkit use
4
- | * 0caa43e index on master: 2ddda12 Update docs & details to fork the package for httptoolkit use
5
- |/
6
- * 2ddda12 (HEAD -> master) Update docs & details to fork the package for httptoolkit use
7
- * ac0a75f Expose peeked HTTP data as __httpPeekedData
8
- * 247cd34 Drop support for Node < 8, and simplify
9
- * f2652ea (origin/no-half-open-tls, no-half-open-tls) Don't allow half-open TLS connections
10
- * 1c6c4af (tag: v0.1.2) bump version
11
- * 475e6f4 lib: ignore pre-detection errors
12
- * ef54b90 readme: add section about how the module works
13
- * f78eb12 (tag: v0.1.1) bump version
14
- * f98b92a fix for node 0.11+ again
15
- * 1af5506 (tag: v0.1.0) bump version
16
- * 1f20a4a fix node v0.11+ compatibility
17
- * bc43948 (tag: v0.0.2) bump version
18
- * 2b06da2 avoid .call()
19
- * a6f4e7e be more strict about byte check
20
- * 2c33984 readme: add http->https redirect example
21
- * 153cbdf (tag: v0.0.1) bump version
22
- * b62bc44 Initial commit
package/test/common.js DELETED
@@ -1,43 +0,0 @@
1
- exports.mustCall = mustCall;
2
- function mustCall(fn, expected) {
3
- if (typeof expected !== 'number')
4
- expected = 1;
5
-
6
- var context = {
7
- expected: expected,
8
- actual: 0,
9
- stack: (new Error()).stack,
10
- name: fn.name || '<anonymous>'
11
- };
12
-
13
- // add the exit listener only once to avoid listener leak warnings
14
- if (mustCall.checks.length === 0)
15
- process.on('exit', mustCall.runChecks);
16
-
17
- mustCall.checks.push(context);
18
-
19
- return function() {
20
- context.actual++;
21
- return fn.apply(this, arguments);
22
- };
23
- }
24
- mustCall.checks = [];
25
- mustCall.runChecks = function(exitCode) {
26
- if (exitCode !== 0)
27
- return;
28
-
29
- var failed = mustCall.checks.filter(function(context) {
30
- return context.actual !== context.expected;
31
- });
32
-
33
- failed.forEach(function(context) {
34
- console.log('Mismatched %s function calls. Expected %d, actual %d.',
35
- context.name,
36
- context.expected,
37
- context.actual);
38
- console.log(context.stack.split('\n').slice(2).join('\n'));
39
- });
40
-
41
- if (failed.length)
42
- process.exit(1);
43
- };
@@ -1,22 +0,0 @@
1
- -----BEGIN CERTIFICATE-----
2
- MIIDjzCCAnegAwIBAgIJANiEfJkuqhEaMA0GCSqGSIb3DQEBCwUAMF4xCzAJBgNV
3
- BAYTAlVTMQ0wCwYDVQQIDARPaGlvMQ8wDQYDVQQHDAZEYXl0b24xEjAQBgNVBAoM
4
- CUZvbywgSW5jLjEbMBkGA1UEAwwSbXlob3N0LmxvY2FsZG9tYWluMB4XDTE2MTEy
5
- NjE3MTUxNFoXDTE2MTIyNjE3MTUxNFowXjELMAkGA1UEBhMCVVMxDTALBgNVBAgM
6
- BE9oaW8xDzANBgNVBAcMBkRheXRvbjESMBAGA1UECgwJRm9vLCBJbmMuMRswGQYD
7
- VQQDDBJteWhvc3QubG9jYWxkb21haW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
8
- ggEKAoIBAQDi8OEKkQogjasE908B/yBHqYFtbYfYJJpq3qsxV7oWWLuygEvVTM/O
9
- edAt0CrXppgh1VxKIbyxkQtf+2TyoUlWxUK/9iYzXolTMQ82X+WeeClPdemoNIYV
10
- wCKrZObJ1TtBM8v2mL8NfQZumr9NahOfK6pWWuLosKtXviJuzAVKQCm3r0scbzi/
11
- avSP63LfY/ua7m29PZI4wflLAghAUbNAaefp9UAxgYQigXuIj+lMDhIdfuGEndS+
12
- xkcW/tqeRr42r4xh1C7lb/FagFqMXA5+wDMn1Tj0JBzQLZMR4Ea2vj24BXuPuA8P
13
- Kq6wvbpZ4B3k+3jrWNL2i3mPUFFXriP3AgMBAAGjUDBOMB0GA1UdDgQWBBSuHVhn
14
- CAGxnAE81k3uq1oC+TIRsTAfBgNVHSMEGDAWgBSuHVhnCAGxnAE81k3uq1oC+TIR
15
- sTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCTk27sW0x9CRPC9ZQs
16
- FuhhcJQb7KXxhUikctkQC101+xZHE0VZeBLgkHCr8Q1tRzqJuvQO6Q5CRB8b5DIX
17
- ZHOEtzjNnm8yoMDCWVb/4G8repGacpHDkZv3jZguHxpTYVJlwvdDCr0SGqfpcF4e
18
- 5MY8DgfsmXo2hqKp/FozTA1MKs3esuxdrZBxhvoQpRsvX0mxfiBNoTWVu2BgjeqP
19
- 11vSPK1z2YCxHkt1SvwrpycfGV8x7qNvEIn+zzxPitYJHro0w0WX8aelT7Xtj2mD
20
- pkLsYhM1dHZkIc+uXVSv+EUUkfLJ1FDJ5D+yyKifyYtBVEOvrDpfUFNKy+bZRYsy
21
- Lcdy
22
- -----END CERTIFICATE-----
@@ -1,28 +0,0 @@
1
- -----BEGIN PRIVATE KEY-----
2
- MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDi8OEKkQogjasE
3
- 908B/yBHqYFtbYfYJJpq3qsxV7oWWLuygEvVTM/OedAt0CrXppgh1VxKIbyxkQtf
4
- +2TyoUlWxUK/9iYzXolTMQ82X+WeeClPdemoNIYVwCKrZObJ1TtBM8v2mL8NfQZu
5
- mr9NahOfK6pWWuLosKtXviJuzAVKQCm3r0scbzi/avSP63LfY/ua7m29PZI4wflL
6
- AghAUbNAaefp9UAxgYQigXuIj+lMDhIdfuGEndS+xkcW/tqeRr42r4xh1C7lb/Fa
7
- gFqMXA5+wDMn1Tj0JBzQLZMR4Ea2vj24BXuPuA8PKq6wvbpZ4B3k+3jrWNL2i3mP
8
- UFFXriP3AgMBAAECggEARjVFOdKjMm0Bkpi8DZ8TKnhrPSJcm2a/iv52Md61CELN
9
- VqzQSR3pUDRpTjMPfgXhHN54HcsQKFL6FOieU13IZZrDSsXpDY1aqK0NysGiNQNx
10
- rE6LSelt7f6x+xpNN/XKziIrIJAi0xZxzff75QRDK8QDf5HAj0JQz+VXm7VskYpw
11
- 90YdekO5S/7V8UlLDDQFcKVdsmiPoDogPz6C2CnzD/yVzduJhmoM9PqccRyA/WVD
12
- YHejPs+LGhM5SLQaMmMWXX5oQ21Gc/t1xvrHokiqcBpxz1djT8I4amRg7D+3bsNG
13
- d/cRLTKLj1joxJl2VQi+cXT45P5VF5inLwXvraJomQKBgQD0F9FcwhbINjtHFSsr
14
- gexUjwynvAZyNkI+rROku37wBtT4EwMkRgq9FN6aHZZ8wi6kxRVH7KbmGKXmPkIw
15
- H/t2lHsae7GZ207H9LZa+VLfDMYCSXa40SvGRBrSPOrTOMCc3HgmP0s/00xfALzs
16
- T4IHJbOYwhzArZhwC6YLn9byFQKBgQDuAt6QxiJneUM/1JW5P7TEzGLi9bwUmSKH
17
- EkLbsDKRmqH1EvEoQaG9js9FBLBKaSRYRnub3FbRb7Ckk1MpQY67rOb8L2ypp7uy
18
- +Bv2Cmbm4mD+/Aapw5Is/6UqNCqTChjI58437eEtZx7X4cYBLwiaKPvPUWI6L2wJ
19
- 4nMsr3fc2wKBgQCpaqqelfvYBIQKJzAqZ2fPnOXsub1DolNCS0CaEqTdFfDVKeUB
20
- VTf42rZSA31CpEhZhozpueBxTeQ/tTCdVGVlfVMgI4A2SJgagsfaxrf1Jll8lt63
21
- Ej8uwnBXQX6/EeHmPcOK0F17ND4Kpml6HwkhytInkXsBZLur8PnTkaJPrQKBgQCN
22
- kF9YtMBZ0yJQoNy85ktakkZuv8IybjK/K/lgOZiaSeLypWWSkBbnbD2Ty4ofeBIJ
23
- /0IeHhv1Tf0+pfHcpAWFUv3AGWUEM6PMew4GdYFm6lbO0pAUASK8aQGP7J81/ddo
24
- B5f8ZBx+qMsLlFn08kiniKDdWoaWHQahinL+rQ8Z6QKBgQDN1SpQnFHYy3uugmN0
25
- KZjDjVs9gBo0lhNVE/HUbf91TiY/ITpjUCHmv+N3VxKyCII4O7+jjFhzK+5NKLV2
26
- WsYLN9ttR/2FQ4JnTUocXple3DxmaA7rCRZj8ZQ/jrGwbEj5nLyOIDblWgaaADMB
27
- W2YC7grwofz3HqqSUCHySDHvUA==
28
- -----END PRIVATE KEY-----
@@ -1,21 +0,0 @@
1
- var fs = require('fs');
2
- var exec = require('child_process').exec;
3
- var assert = require('assert');
4
-
5
- var common = require(__dirname + '/common');
6
- var httpolyglot = require(__dirname + '/../lib/index');
7
-
8
- var srv = httpolyglot.createServer({
9
- key: fs.readFileSync(__dirname + '/fixtures/server.key'),
10
- cert: fs.readFileSync(__dirname + '/fixtures/server.crt')
11
- }, function(req, res) {
12
- assert(false, 'Request handler should not be called');
13
- });
14
- srv.listen(0, '127.0.0.1', common.mustCall(function() {
15
- var port = this.address().port;
16
-
17
- exec('nmap 127.0.0.1 -p' + port,
18
- common.mustCall(function(err, stdout, stderr) {
19
- srv.close();
20
- }));
21
- }));
@@ -1,45 +0,0 @@
1
- var fs = require('fs');
2
- var http = require('http');
3
- var https = require('https');
4
- var assert = require('assert');
5
-
6
- var common = require(__dirname + '/common');
7
- var httpolyglot = require(__dirname + '/../lib/index');
8
-
9
- var srv = httpolyglot.createServer({
10
- key: fs.readFileSync(__dirname + '/fixtures/server.key'),
11
- cert: fs.readFileSync(__dirname + '/fixtures/server.crt')
12
- }, common.mustCall(function(req, res) {
13
- this.count || (this.count = 0);
14
- res.end(req.socket.encrypted ? 'https' : 'http');
15
- if (++this.count === 2)
16
- this.close();
17
- }, 2));
18
- srv.listen(0, '127.0.0.1', common.mustCall(function() {
19
- var port = this.address().port;
20
-
21
- http.get({
22
- host: '127.0.0.1',
23
- port: port
24
- }, common.mustCall(function(res) {
25
- var body = '';
26
- res.on('data', function(data) {
27
- body += data;
28
- }).on('end', common.mustCall(function() {
29
- assert.strictEqual(body, 'http');
30
- }));
31
- }));
32
-
33
- https.get({
34
- host: '127.0.0.1',
35
- port: port,
36
- rejectUnauthorized: false
37
- }, common.mustCall(function(res) {
38
- var body = '';
39
- res.on('data', function(data) {
40
- body += data;
41
- }).on('end', common.mustCall(function() {
42
- assert.strictEqual(body, 'https');
43
- }));
44
- }));
45
- }));
package/test/test.js DELETED
@@ -1,4 +0,0 @@
1
- require('fs').readdirSync(__dirname).forEach(function(f) {
2
- if (f.substr(0, 5) === 'test-')
3
- require('./' + f);
4
- });