mockttp
Version:
Mock HTTP server for testing HTTP clients and stubbing webservices
122 lines (108 loc) • 4.99 kB
text/typescript
import type * as streams from 'stream';
import type * as net from 'net';
import type * as tls from 'tls';
import { TlsSocketMetadata } from '../types';
// We store a bunch of metadata that we directly attach to sockets, TLS
// sockets, and HTTP/2 streams to track our state over time & through tunneling:
export const InitialRemoteAddress = Symbol('initial-remote-address');
export const InitialRemotePort = Symbol('initial-port-address');
export const TlsSetupCompleted = Symbol('tls-setup-comleted');
export const LastHopEncrypted = Symbol('last-hop-encrypted');
export const LastTunnelAddress = Symbol('last-hop-address');
export const TlsMetadata = Symbol('tls-metadata');
export const ClientErrorInProgress = Symbol('client-error-in-progress');
export const SocketTimingInfo = Symbol('socket-timing-info');
export const SocketMetadata = Symbol('socket-metadata');
export interface SocketMetadata {
tags?: string[];
[key: string]: any;
}
declare module 'net' {
interface Socket {
/**
* Is this socket trying to send encrypted data upstream? For direct connections
* this always matches socket.encrypted. For CONNECT-proxied connections (where
* the initial connection could be HTTPS and the upstream connection HTTP, or
* vice versa) all on one socket, this is the value for the final hop.
*/
[LastHopEncrypted]?: boolean;
/**
* The hostname + maybe port from the inner-most tunnel request powering this
* socket. This is the best signal for the client's real target address,
* if provided. It's not set at all for direct (non-tunnelled) connections.
*/
[LastTunnelAddress]?: string;
/**
* If there's a client error being sent, we track the corresponding packet
* data on the socket, so that when it fires repeatedly we can combine them
* into a single response & error event.
*/
[ClientErrorInProgress]?: { rawPacket?: Buffer };
/**
* Our recordings of various timestamps, used for monitoring &
* performance analysis later on
*/
[SocketTimingInfo]?: {
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
lastRequestTimestamp?: number; // Latest request or websocket request time, if any
}
// Set on TLSSocket, defined here for convenient access on _all_ sockets
[TlsMetadata]?: TlsSocketMetadata;
[InitialRemoteAddress]?: string;
[InitialRemotePort]?: number;
/**
* Arbitrary custom metadata that may be added during socket processing,
* e.g. with the SOCKS custom-metadata auth extension.
*
* Currently the only metadata that is exposed is `tags`, which are
* attached to each request on this connection with a `socket-metadata:`
* prefix. This can be used to provide tags during SOCKS connection
* setup that will then be visible on all 'response' event data (for
* example) later on.
*/
[SocketMetadata]?: SocketMetadata;
}
}
declare module 'tls' {
interface TLSSocket {
/**
* Have we seen evidence that the client has completed & trusts the connection?
* If set, we know that errors are client errors, not TLS setup/trust issues.
*/
[TlsSetupCompleted]?: boolean;
/**
* Extra metadata attached to a TLS socket, taken from the client hello and
* preceeding tunneling steps.
*/
[TlsMetadata]?: TlsSocketMetadata;
/**
* We cache this extra metadata during the initial TLS setup on these separate
* properties, because it can be cleared & lost from the socket in some
* TLS error scenarios.
*/
[InitialRemoteAddress]?: string;
[InitialRemotePort]?: number;
}
}
declare module 'http2' {
class Http2Session {
// session.socket is cleared before error handling kicks in. That's annoying,
// so we manually preserve the socket elsewhere to work around it.
initialSocket?: net.Socket;
}
class ServerHttp2Stream {
// Treated the same as net.Socket, when we unwrap them in our combo server:
[LastHopEncrypted]?: net.Socket[typeof LastHopEncrypted];
[LastTunnelAddress]?: net.Socket[typeof LastTunnelAddress];
[SocketTimingInfo]?: net.Socket[typeof SocketTimingInfo];
[SocketMetadata]?: SocketMetadata;
}
}
export type SocketIsh<MinProps extends keyof net.Socket & keyof tls.TLSSocket> =
streams.Duplex &
Partial<Pick<net.Socket, MinProps>> &
Partial<Pick<tls.TLSSocket, MinProps>>;