Comparing version 1.0.4 to 1.0.5
@@ -31,5 +31,8 @@ // There's a few places where we attach extra data to some node objects during | ||
__timingInfo?: { | ||
initialSocket?: number; // Initial raw socket time | ||
tunnelSetup?: number; // Latest CONNECT completion, if any | ||
tlsConnected?: number; // Latest TLS handshake completion, if any | ||
initialSocket: number; // Initial raw socket time, since unix epoch | ||
// High-precision timestamps: | ||
initialSocketTimestamp: number; | ||
tunnelSetupTimestamp?: number; // Latest CONNECT completion, if any | ||
tlsConnectedTimestamp?: number; // Latest TLS handshake completion, if any | ||
}; | ||
@@ -53,5 +56,6 @@ | ||
// We cache the initially set remote address on sockets, because it's cleared | ||
// We cache the initially set remote address & port on sockets, because it's cleared | ||
// before the TLS error callback is called, exactly when we want to read it. | ||
initialRemoteAddress?: string; | ||
initialRemotePort?: number; | ||
@@ -58,0 +62,0 @@ // Marker used to detect whether client errors should be reported as TLS issues |
@@ -59,2 +59,3 @@ /** | ||
private typeHasField; | ||
private optionalField; | ||
private typeHasInputField; | ||
@@ -61,0 +62,0 @@ enableDebug(): void; |
@@ -136,3 +136,3 @@ "use strict"; | ||
id, | ||
${this.typeHasField('MockedEndpoint', 'explanation') ? 'explanation' : ''} | ||
${this.optionalField('MockedEndpoint', 'explanation')} | ||
} | ||
@@ -157,3 +157,3 @@ }`, { | ||
id, | ||
${this.typeHasField('MockedEndpoint', 'explanation') ? 'explanation' : ''} | ||
${this.optionalField('MockedEndpoint', 'explanation')} | ||
} | ||
@@ -177,6 +177,6 @@ }`, { | ||
body, | ||
${this.typeHasField('Request', 'timingEvents') ? 'timingEvents' : ''} | ||
${this.typeHasField('Request', 'httpVersion') ? 'httpVersion' : ''} | ||
${this.optionalField('Request', 'timingEvents')} | ||
${this.optionalField('Request', 'httpVersion')} | ||
} | ||
${this.typeHasField('MockedEndpoint', 'isPending') ? 'isPending' : ''} | ||
${this.optionalField('MockedEndpoint', 'isPending')} | ||
} | ||
@@ -326,2 +326,7 @@ }`, { | ||
} | ||
optionalField(typeName, fieldName) { | ||
return (this.typeHasField(typeName, fieldName)) | ||
? fieldName | ||
: ''; | ||
} | ||
typeHasInputField(typeName, fieldName) { | ||
@@ -351,3 +356,3 @@ const type = _.find(this.mockServerSchema.types, { name: typeName }); | ||
id, | ||
${this.typeHasField('MockedEndpoint', 'explanation') ? 'explanation' : ''} | ||
${this.optionalField('MockedEndpoint', 'explanation')} | ||
} | ||
@@ -410,3 +415,3 @@ }`); | ||
httpVersion, | ||
${this.typeHasField('InitiatedRequest', 'tags') ? 'tags' : ''} | ||
${this.optionalField('InitiatedRequest', 'tags')} | ||
} | ||
@@ -420,3 +425,3 @@ }` | ||
id, | ||
${this.typeHasField('Request', 'matchedRuleId') ? 'matchedRuleId' : ''} | ||
${this.optionalField('Request', 'matchedRuleId')} | ||
protocol, | ||
@@ -430,5 +435,5 @@ method, | ||
body, | ||
${this.typeHasField('Request', 'timingEvents') ? 'timingEvents' : ''} | ||
${this.typeHasField('Request', 'httpVersion') ? 'httpVersion' : ''} | ||
${this.typeHasField('Request', 'tags') ? 'tags' : ''} | ||
${this.optionalField('Request', 'timingEvents')} | ||
${this.optionalField('Request', 'httpVersion')} | ||
${this.optionalField('Request', 'tags')} | ||
} | ||
@@ -446,4 +451,4 @@ }` | ||
body, | ||
${this.typeHasField('Response', 'timingEvents') ? 'timingEvents' : ''} | ||
${this.typeHasField('Response', 'tags') ? 'tags' : ''} | ||
${this.optionalField('Response', 'timingEvents')} | ||
${this.optionalField('Response', 'tags')} | ||
} | ||
@@ -465,4 +470,4 @@ }` | ||
body, | ||
${this.typeHasField('Response', 'timingEvents') ? 'timingEvents' : ''} | ||
${this.typeHasField('Response', 'tags') ? 'tags' : ''} | ||
${this.optionalField('Response', 'timingEvents')} | ||
${this.optionalField('Response', 'tags')} | ||
} | ||
@@ -478,3 +483,5 @@ }` | ||
remoteIpAddress | ||
${this.typeHasField('TlsRequest', 'tags') ? 'tags' : ''} | ||
${this.optionalField('TlsRequest', 'remotePort')} | ||
${this.optionalField('TlsRequest', 'tags')} | ||
${this.optionalField('TlsRequest', 'timingEvents')} | ||
} | ||
@@ -481,0 +488,0 @@ }` |
@@ -16,2 +16,3 @@ "use strict"; | ||
const httpolyglot = require("@httptoolkit/httpolyglot"); | ||
const now = require("performance-now"); | ||
const destroyable_server_1 = require("../util/destroyable-server"); | ||
@@ -32,3 +33,3 @@ const tls_1 = require("../util/tls"); | ||
_handle.oncertcb = function (info) { | ||
var _a, _b, _c, _d, _e; | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j; | ||
tlsSocket.servername = info.servername; | ||
@@ -38,3 +39,4 @@ tlsSocket.initialRemoteAddress = tlsSocket.remoteAddress || // Normal case | ||
((_d = (_c = (_b = tlsSocket._handle) === null || _b === void 0 ? void 0 : _b._parentWrap) === null || _c === void 0 ? void 0 : _c.stream) === null || _d === void 0 ? void 0 : _d.remoteAddress); // For HTTP/2 CONNECT | ||
return (_e = loadSNI) === null || _e === void 0 ? void 0 : _e.apply(this, arguments); | ||
tlsSocket.initialRemotePort = tlsSocket.remotePort || ((_e = tlsSocket._parent) === null || _e === void 0 ? void 0 : _e.remotePort) || ((_h = (_g = (_f = tlsSocket._handle) === null || _f === void 0 ? void 0 : _f._parentWrap) === null || _g === void 0 ? void 0 : _g.stream) === null || _h === void 0 ? void 0 : _h.remotePort); | ||
return (_j = loadSNI) === null || _j === void 0 ? void 0 : _j.apply(this, arguments); | ||
}; | ||
@@ -54,7 +56,9 @@ }; | ||
// than the initial TLS handshake for an unhappy disconnection. | ||
const timing = socket.__timingInfo || {}; | ||
const tlsSetupDuration = timing.tlsConnected - (timing.tunnelSetup || timing.initialSocket); | ||
const maxTlsRejectionTime = (!Object.is(tlsSetupDuration, NaN) && tlsSetupDuration !== 0) | ||
? tlsSetupDuration * 10 | ||
: 5000; | ||
const timing = socket.__timingInfo; | ||
const tlsSetupDuration = timing | ||
? timing.tlsConnectedTimestamp - (timing.tunnelSetupTimestamp || timing.initialSocketTimestamp) | ||
: 0; | ||
const maxTlsRejectionTime = !Object.is(tlsSetupDuration, NaN) | ||
? Math.max(tlsSetupDuration * 10, 100) // Ensure a sensible minimum | ||
: 2000; | ||
util_1.delay(maxTlsRejectionTime).then(resolve); | ||
@@ -88,3 +92,3 @@ }) | ||
? 'reset' | ||
: 'unknown'; // Something else. | ||
: 'unknown'; // Something \else. | ||
if (cause === 'unknown') | ||
@@ -94,2 +98,28 @@ console.log('Unknown TLS error:', error); | ||
} | ||
function buildTimingInfo() { | ||
return { initialSocket: Date.now(), initialSocketTimestamp: now() }; | ||
} | ||
function buildTlsError(socket, cause) { | ||
var _a, _b, _c; | ||
const timingInfo = socket.__timingInfo || ((_a = socket._parent) === null || _a === void 0 ? void 0 : _a.__timingInfo) || | ||
buildTimingInfo(); | ||
return { | ||
failureCause: cause, | ||
hostname: socket.servername, | ||
// These only work because of oncertcb monkeypatch above | ||
remoteIpAddress: socket.remoteAddress || // Normal case | ||
((_b = socket._parent) === null || _b === void 0 ? void 0 : _b.remoteAddress) || // Pre-certCB error, e.g. timeout | ||
socket.initialRemoteAddress, | ||
remotePort: socket.remotePort || ((_c = socket._parent) === null || _c === void 0 ? void 0 : _c.remotePort) || | ||
socket.initialRemotePort, | ||
tags: [], | ||
timingEvents: { | ||
startTime: timingInfo.initialSocket, | ||
connectTimestamp: timingInfo.initialSocketTimestamp, | ||
tunnelTimestamp: timingInfo.tunnelSetupTimestamp, | ||
handshakeTimestamp: timingInfo.tlsConnectedTimestamp, | ||
failureTimestamp: now() | ||
} | ||
}; | ||
} | ||
// The low-level server that handles all the sockets & TLS. The server will correctly call the | ||
@@ -136,3 +166,3 @@ // given handler for both HTTP & HTTPS direct connections, or connections when used as an | ||
server.on('connection', (socket) => { | ||
socket.__timingInfo = socket.__timingInfo || { initialSocket: Date.now() }; | ||
socket.__timingInfo = socket.__timingInfo || buildTimingInfo(); | ||
// All sockets are initially marked as using unencrypted upstream connections. | ||
@@ -151,13 +181,8 @@ // If TLS is used, this is upgraded to 'true' by secureConnection below. | ||
else if (!socket.__timingInfo) { | ||
socket.__timingInfo = { initialSocket: Date.now() }; | ||
socket.__timingInfo = buildTimingInfo(); | ||
} | ||
socket.__timingInfo.tlsConnected = Date.now(); | ||
socket.__timingInfo.tlsConnectedTimestamp = now(); | ||
socket.lastHopEncrypted = true; | ||
ifTlsDropped(socket, () => { | ||
tlsClientErrorListener(socket, { | ||
failureCause: 'closed', | ||
hostname: socket.servername, | ||
remoteIpAddress: socket.remoteAddress || socket.initialRemoteAddress, | ||
tags: [] | ||
}); | ||
tlsClientErrorListener(socket, buildTlsError(socket, 'closed')); | ||
}); | ||
@@ -173,12 +198,3 @@ }); | ||
server.on('tlsClientError', (error, socket) => { | ||
var _a; | ||
// These only work because of oncertcb monkeypatch above | ||
tlsClientErrorListener(socket, { | ||
failureCause: getCauseFromError(error), | ||
hostname: socket.servername, | ||
remoteIpAddress: socket.remoteAddress || // Normal case | ||
((_a = socket._parent) === null || _a === void 0 ? void 0 : _a.remoteAddress) || // Pre-certCB error, e.g. timeout | ||
socket.initialRemoteAddress, | ||
tags: [] | ||
}); | ||
tlsClientErrorListener(socket, buildTlsError(socket, getCauseFromError(error))); | ||
}); | ||
@@ -207,3 +223,3 @@ // If the server receives a HTTP/HTTPS CONNECT request, Pretend to tunnel, then just re-handle: | ||
socket.write('HTTP/' + req.httpVersion + ' 200 OK\r\n\r\n', 'utf-8', () => { | ||
socket.__timingInfo.tunnelSetup = Date.now(); | ||
socket.__timingInfo.tunnelSetupTimestamp = now(); | ||
server.emit('connection', socket); | ||
@@ -210,0 +226,0 @@ }); |
@@ -46,5 +46,14 @@ /** | ||
remoteIpAddress: string; | ||
remotePort: number; | ||
failureCause: 'closed' | 'reset' | 'cert-rejected' | 'no-shared-cipher' | 'unknown'; | ||
tags: string[]; | ||
timingEvents: TlsTimingEvents; | ||
} | ||
export interface TlsTimingEvents { | ||
startTime: number; | ||
connectTimestamp: number; | ||
failureTimestamp: number; | ||
handshakeTimestamp?: number; | ||
tunnelTimestamp?: number; | ||
} | ||
export interface OngoingRequest extends Request, EventEmitter { | ||
@@ -51,0 +60,0 @@ body: ParsedBody; |
{ | ||
"name": "mockttp", | ||
"version": "1.0.4", | ||
"version": "1.0.5", | ||
"description": "Mock HTTP server for testing HTTP clients and stubbing webservices", | ||
@@ -5,0 +5,0 @@ "main": "dist/main.js", |
@@ -287,2 +287,8 @@ /** | ||
private optionalField(typeName: string, fieldName: string): string { | ||
return (this.typeHasField(typeName, fieldName)) | ||
? fieldName | ||
: ''; | ||
} | ||
private typeHasInputField(typeName: string, fieldName: string): boolean { | ||
@@ -351,3 +357,3 @@ const type: any = _.find(this.mockServerSchema.types, { name: typeName }); | ||
id, | ||
${this.typeHasField('MockedEndpoint', 'explanation') ? 'explanation' : ''} | ||
${this.optionalField('MockedEndpoint', 'explanation')} | ||
} | ||
@@ -377,3 +383,3 @@ }`, { | ||
id, | ||
${this.typeHasField('MockedEndpoint', 'explanation') ? 'explanation' : ''} | ||
${this.optionalField('MockedEndpoint', 'explanation')} | ||
} | ||
@@ -420,3 +426,3 @@ }` | ||
id, | ||
${this.typeHasField('MockedEndpoint', 'explanation') ? 'explanation' : ''} | ||
${this.optionalField('MockedEndpoint', 'explanation')} | ||
} | ||
@@ -480,3 +486,3 @@ }`, { | ||
httpVersion, | ||
${this.typeHasField('InitiatedRequest', 'tags') ? 'tags' : ''} | ||
${this.optionalField('InitiatedRequest', 'tags')} | ||
} | ||
@@ -490,3 +496,3 @@ }` | ||
id, | ||
${this.typeHasField('Request', 'matchedRuleId') ? 'matchedRuleId' : ''} | ||
${this.optionalField('Request', 'matchedRuleId')} | ||
protocol, | ||
@@ -500,5 +506,5 @@ method, | ||
body, | ||
${this.typeHasField('Request', 'timingEvents') ? 'timingEvents' : ''} | ||
${this.typeHasField('Request', 'httpVersion') ? 'httpVersion' : ''} | ||
${this.typeHasField('Request', 'tags') ? 'tags' : ''} | ||
${this.optionalField('Request', 'timingEvents')} | ||
${this.optionalField('Request', 'httpVersion')} | ||
${this.optionalField('Request', 'tags')} | ||
} | ||
@@ -516,4 +522,4 @@ }` | ||
body, | ||
${this.typeHasField('Response', 'timingEvents') ? 'timingEvents' : ''} | ||
${this.typeHasField('Response', 'tags') ? 'tags' : ''} | ||
${this.optionalField('Response', 'timingEvents')} | ||
${this.optionalField('Response', 'tags')} | ||
} | ||
@@ -535,4 +541,4 @@ }` | ||
body, | ||
${this.typeHasField('Response', 'timingEvents') ? 'timingEvents' : ''} | ||
${this.typeHasField('Response', 'tags') ? 'tags' : ''} | ||
${this.optionalField('Response', 'timingEvents')} | ||
${this.optionalField('Response', 'tags')} | ||
} | ||
@@ -548,3 +554,5 @@ }` | ||
remoteIpAddress | ||
${this.typeHasField('TlsRequest', 'tags') ? 'tags' : ''} | ||
${this.optionalField('TlsRequest', 'remotePort')} | ||
${this.optionalField('TlsRequest', 'tags')} | ||
${this.optionalField('TlsRequest', 'timingEvents')} | ||
} | ||
@@ -642,6 +650,6 @@ }` | ||
body, | ||
${this.typeHasField('Request', 'timingEvents') ? 'timingEvents' : ''} | ||
${this.typeHasField('Request', 'httpVersion') ? 'httpVersion' : ''} | ||
${this.optionalField('Request', 'timingEvents')} | ||
${this.optionalField('Request', 'httpVersion')} | ||
} | ||
${this.typeHasField('MockedEndpoint', 'isPending') ? 'isPending' : ''} | ||
${this.optionalField('MockedEndpoint', 'isPending')} | ||
} | ||
@@ -648,0 +656,0 @@ }`, { |
@@ -7,4 +7,4 @@ import _ = require('lodash'); | ||
import * as streams from 'stream'; | ||
import SocketWrapper = require('_stream_wrap'); | ||
import httpolyglot = require('@httptoolkit/httpolyglot'); | ||
import now = require("performance-now"); | ||
@@ -33,2 +33,5 @@ import { TlsRequest } from '../types'; | ||
tlsSocket._handle?._parentWrap?.stream?.remoteAddress; // For HTTP/2 CONNECT | ||
tlsSocket.initialRemotePort = tlsSocket.remotePort || | ||
tlsSocket._parent?.remotePort || | ||
tlsSocket._handle?._parentWrap?.stream?.remotePort; | ||
@@ -59,7 +62,10 @@ return loadSNI?.apply(this, arguments as any); | ||
// than the initial TLS handshake for an unhappy disconnection. | ||
const timing = socket.__timingInfo || {}; | ||
const tlsSetupDuration = timing.tlsConnected! - (timing.tunnelSetup! || timing.initialSocket!); | ||
const maxTlsRejectionTime = (!Object.is(tlsSetupDuration, NaN) && tlsSetupDuration !== 0) | ||
? tlsSetupDuration * 10 | ||
: 5000; | ||
const timing = socket.__timingInfo; | ||
const tlsSetupDuration = timing | ||
? timing.tlsConnectedTimestamp! - (timing.tunnelSetupTimestamp! || timing.initialSocketTimestamp) | ||
: 0; | ||
const maxTlsRejectionTime = !Object.is(tlsSetupDuration, NaN) | ||
? Math.max(tlsSetupDuration * 10, 100) // Ensure a sensible minimum | ||
: 2000; | ||
delay(maxTlsRejectionTime).then(resolve); | ||
@@ -94,3 +100,3 @@ }) | ||
? 'reset' | ||
: 'unknown'; // Something else. | ||
: 'unknown'; // Something \else. | ||
@@ -102,2 +108,35 @@ if (cause === 'unknown') console.log('Unknown TLS error:', error); | ||
function buildTimingInfo(): Required<net.Socket>['__timingInfo'] { | ||
return { initialSocket: Date.now(), initialSocketTimestamp: now() }; | ||
} | ||
function buildTlsError( | ||
socket: tls.TLSSocket, | ||
cause: TlsRequest['failureCause'] | ||
): TlsRequest { | ||
const timingInfo = socket.__timingInfo || | ||
socket._parent?.__timingInfo || | ||
buildTimingInfo(); | ||
return { | ||
failureCause: cause, | ||
hostname: socket.servername, | ||
// These only work because of oncertcb monkeypatch above | ||
remoteIpAddress: socket.remoteAddress || // Normal case | ||
socket._parent?.remoteAddress || // Pre-certCB error, e.g. timeout | ||
socket.initialRemoteAddress!, // Recorded by certCB monkeypatch | ||
remotePort: socket.remotePort || | ||
socket._parent?.remotePort || | ||
socket.initialRemotePort!, | ||
tags: [], | ||
timingEvents: { | ||
startTime: timingInfo.initialSocket, | ||
connectTimestamp: timingInfo.initialSocketTimestamp, | ||
tunnelTimestamp: timingInfo.tunnelSetupTimestamp, | ||
handshakeTimestamp: timingInfo.tlsConnectedTimestamp, | ||
failureTimestamp: now() | ||
} | ||
}; | ||
} | ||
// The low-level server that handles all the sockets & TLS. The server will correctly call the | ||
@@ -147,3 +186,3 @@ // given handler for both HTTP & HTTPS direct connections, or connections when used as an | ||
server.on('connection', (socket: net.Socket) => { | ||
socket.__timingInfo = socket.__timingInfo || { initialSocket: Date.now() }; | ||
socket.__timingInfo = socket.__timingInfo || buildTimingInfo(); | ||
@@ -163,15 +202,10 @@ // All sockets are initially marked as using unencrypted upstream connections. | ||
} else if (!socket.__timingInfo) { | ||
socket.__timingInfo = { initialSocket: Date.now() }; | ||
socket.__timingInfo = buildTimingInfo(); | ||
} | ||
socket.__timingInfo!.tlsConnected = Date.now(); | ||
socket.__timingInfo!.tlsConnectedTimestamp = now(); | ||
socket.lastHopEncrypted = true; | ||
ifTlsDropped(socket, () => { | ||
tlsClientErrorListener(socket, { | ||
failureCause: 'closed', | ||
hostname: socket.servername, | ||
remoteIpAddress: socket.remoteAddress || socket.initialRemoteAddress!, | ||
tags: [] | ||
}); | ||
tlsClientErrorListener(socket, buildTlsError(socket, 'closed')); | ||
}); | ||
@@ -189,11 +223,3 @@ }); | ||
server.on('tlsClientError', (error: Error, socket: tls.TLSSocket) => { | ||
// These only work because of oncertcb monkeypatch above | ||
tlsClientErrorListener(socket, { | ||
failureCause: getCauseFromError(error), | ||
hostname: socket.servername, | ||
remoteIpAddress: socket.remoteAddress || // Normal case | ||
socket._parent?.remoteAddress || // Pre-certCB error, e.g. timeout | ||
socket.initialRemoteAddress!, // Recorded by certCB monkeypatch | ||
tags: [] | ||
}); | ||
tlsClientErrorListener(socket, buildTlsError(socket, getCauseFromError(error))); | ||
}); | ||
@@ -228,3 +254,3 @@ | ||
socket.write('HTTP/' + req.httpVersion + ' 200 OK\r\n\r\n', 'utf-8', () => { | ||
socket.__timingInfo!.tunnelSetup = Date.now(); | ||
socket.__timingInfo!.tunnelSetupTimestamp = now(); | ||
server.emit('connection', socket); | ||
@@ -231,0 +257,0 @@ }); |
@@ -62,6 +62,19 @@ /** | ||
remoteIpAddress: string; | ||
remotePort: number; | ||
failureCause: 'closed' | 'reset' | 'cert-rejected' | 'no-shared-cipher' | 'unknown'; | ||
tags: string[]; | ||
timingEvents: TlsTimingEvents; | ||
} | ||
export interface TlsTimingEvents { | ||
startTime: number; // Ms since unix epoch | ||
// High-precision floating-point monotonically increasing timestamps. | ||
// Comparable and precise, but not related to specific current time. | ||
connectTimestamp: number; // When the socket initially connected | ||
failureTimestamp: number; // When the error occurred | ||
handshakeTimestamp?: number; // When the handshake completed (if it did) | ||
tunnelTimestamp?: number; // When the outer tunnel was create (if present) | ||
} | ||
// Internal representation of an ongoing HTTP request whilst it's being processed | ||
@@ -68,0 +81,0 @@ export interface OngoingRequest extends Request, EventEmitter { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
741067
13464