Mock HTTP server for testing HTTP clients and stubbing webservices
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Comprehensive event system for monitoring all HTTP/WebSocket traffic, TLS connections, errors, and custom rule events to enable detailed testing and debugging.
Monitor the lifecycle of HTTP requests and responses.
interface Mockttp {
/**
* Subscribe to request initiation events (headers received, body may still be streaming).
* Fires as soon as request method, path, and headers are available.
*/
on(event: 'request-initiated', callback: (req: InitiatedRequest) => void): Promise<void>;
/**
* Subscribe to completed request events (full request including body received).
* Fires when the complete request has been received and processed.
*/
on(event: 'request', callback: (req: CompletedRequest) => void): Promise<void>;
/**
* Subscribe to response completion events.
* Fires when a response has been fully sent to the client.
*/
on(event: 'response', callback: (res: CompletedResponse) => void): Promise<void>;
/**
* Subscribe to request abortion events.
* Fires when requests are aborted before completion (client disconnect, timeout, etc.).
*/
on(event: 'abort', callback: (req: AbortedRequest) => void): Promise<void>;
}
interface InitiatedRequest {
id: string;
protocol: string;
httpVersion: string;
method: string;
url: string;
path: string;
headers: Headers;
timingEvents: TimingEvents;
tags: string[];
// Note: No body property - body may still be streaming
}
interface CompletedRequest extends InitiatedRequest {
matchedRuleId?: string;
body: CompletedBody;
rawTrailers: RawTrailers;
trailers: Trailers;
}
interface CompletedResponse {
id: string;
statusCode: number;
statusMessage: string;
headers: Headers;
rawHeaders: RawHeaders;
body: CompletedBody;
rawTrailers: RawTrailers;
trailers: Trailers;
timingEvents: TimingEvents;
tags: string[];
}
interface AbortedRequest extends InitiatedRequest {
error?: {
name?: string;
code?: string;
message?: string;
stack?: string;
};
}Usage Examples:
import { getLocal } from "mockttp";
const mockServer = getLocal();
await mockServer.start();
// Monitor all HTTP traffic
await mockServer.on('request-initiated', (req) => {
console.log(`${req.method} ${req.path} - Started`);
});
await mockServer.on('request', (req) => {
console.log(`${req.method} ${req.path} - Body: ${req.body.buffer.length} bytes`);
});
await mockServer.on('response', (res) => {
console.log(`Response ${res.statusCode} - ${res.body.buffer.length} bytes`);
});
await mockServer.on('abort', (req) => {
console.log(`Request aborted: ${req.method} ${req.path}`);
if (req.error) {
console.log(`Error: ${req.error.message}`);
}
});
// Set up some mock rules
await mockServer.forGet("/api/test").thenReply(200, "OK");Monitor WebSocket connection lifecycle and message traffic.
interface Mockttp {
/**
* Subscribe to WebSocket upgrade requests.
* Fires when a WebSocket upgrade is requested, before acceptance/rejection decision.
*/
on(event: 'websocket-request', callback: (req: CompletedRequest) => void): Promise<void>;
/**
* Subscribe to WebSocket upgrade acceptance events.
* Fires when a WebSocket upgrade is successfully accepted.
*/
on(event: 'websocket-accepted', callback: (res: CompletedResponse) => void): Promise<void>;
/**
* Subscribe to WebSocket messages received from clients.
* Fires for every message received from WebSocket clients.
*/
on(event: 'websocket-message-received', callback: (msg: WebSocketMessage) => void): Promise<void>;
/**
* Subscribe to WebSocket messages sent to clients.
* Fires for every message sent to WebSocket clients.
*/
on(event: 'websocket-message-sent', callback: (msg: WebSocketMessage) => void): Promise<void>;
/**
* Subscribe to WebSocket connection closure events.
* Fires when WebSocket connections are cleanly closed with close frames.
*/
on(event: 'websocket-close', callback: (close: WebSocketClose) => void): Promise<void>;
}
interface WebSocketMessage {
streamId: string;
direction: 'sent' | 'received';
content: Uint8Array;
isBinary: boolean;
eventTimestamp: number;
timingEvents: TimingEvents;
tags: string[];
}
interface WebSocketClose {
streamId: string;
closeCode: number | undefined;
closeReason: string;
timingEvents: TimingEvents;
tags: string[];
}Usage Examples:
import { getLocal } from "mockttp";
const mockServer = getLocal();
await mockServer.start();
// Track WebSocket connections
const connections = new Map();
await mockServer.on('websocket-request', (req) => {
console.log(`WebSocket requested: ${req.url} by ${req.headers['user-agent']}`);
});
await mockServer.on('websocket-accepted', (res) => {
connections.set(res.id, {
connectedAt: Date.now(),
messageCount: 0
});
console.log(`WebSocket ${res.id} connected`);
});
await mockServer.on('websocket-message-received', (msg) => {
const conn = connections.get(msg.streamId);
if (conn) conn.messageCount++;
const text = msg.isBinary ?
`[Binary: ${msg.content.length} bytes]` :
new TextDecoder().decode(msg.content);
console.log(`${msg.streamId} received: ${text}`);
});
await mockServer.on('websocket-message-sent', (msg) => {
const text = msg.isBinary ?
`[Binary: ${msg.content.length} bytes]` :
new TextDecoder().decode(msg.content);
console.log(`${msg.streamId} sent: ${text}`);
});
await mockServer.on('websocket-close', (close) => {
const conn = connections.get(close.streamId);
if (conn) {
const duration = Date.now() - conn.connectedAt;
console.log(`WebSocket ${close.streamId} closed after ${duration}ms, ${conn.messageCount} messages`);
}
connections.delete(close.streamId);
});
// Set up WebSocket mock
await mockServer.forAnyWebSocket().thenEcho();Monitor TLS handshakes, client errors, and connection-level events.
interface Mockttp {
/**
* Subscribe to TLS handshake failure events.
* Fires when TLS connections fail to complete handshake.
*/
on(event: 'tls-client-error', callback: (error: TlsHandshakeFailure) => void): Promise<void>;
/**
* Subscribe to client error events.
* Fires when requests fail before completion due to client errors.
*/
on(event: 'client-error', callback: (error: ClientError) => void): Promise<void>;
/**
* Subscribe to TLS passthrough connection events.
* Fires when TLS connections are passed through without interception.
*/
on(event: 'tls-passthrough-opened', callback: (event: TlsPassthroughEvent) => void): Promise<void>;
/**
* Subscribe to TLS passthrough closure events.
*/
on(event: 'tls-passthrough-closed', callback: (event: TlsPassthroughEvent) => void): Promise<void>;
/**
* Subscribe to raw protocol passthrough events.
* Fires when non-HTTP protocols are passed through.
*/
on(event: 'raw-passthrough-opened', callback: (event: RawPassthroughEvent) => void): Promise<void>;
/**
* Subscribe to raw protocol passthrough closure events.
*/
on(event: 'raw-passthrough-closed', callback: (event: RawPassthroughEvent) => void): Promise<void>;
/**
* Subscribe to raw passthrough data events.
* Fires for each chunk of data in raw passthrough tunnels.
*/
on(event: 'raw-passthrough-data', callback: (event: RawPassthroughDataEvent) => void): Promise<void>;
}
interface TlsHandshakeFailure {
failureCause: 'closed' | 'reset' | 'cert-rejected' | 'no-shared-cipher' | 'handshake-timeout' | 'unknown';
remoteIpAddress?: string;
remotePort?: number;
timingEvents: TlsFailureTimingEvents;
tags: string[];
destination?: Destination;
tlsMetadata: TlsSocketMetadata;
}
interface ClientError {
errorCode?: string;
request: Partial<CompletedRequest>;
response: CompletedResponse | 'aborted';
}
interface TlsPassthroughEvent {
id: string;
destination: Destination;
remoteIpAddress: string;
remotePort: number;
tags: string[];
timingEvents: TlsTimingEvents;
tlsMetadata: TlsSocketMetadata;
}
interface RawPassthroughEvent {
id: string;
destination: Destination;
remoteIpAddress: string;
remotePort: number;
tags: string[];
timingEvents: ConnectionTimingEvents;
}
interface RawPassthroughDataEvent {
id: string;
direction: 'sent' | 'received';
content: Uint8Array;
eventTimestamp: number;
}
interface TlsSocketMetadata {
sniHostname?: string;
clientAlpn?: string[];
ja3Fingerprint?: string;
ja4Fingerprint?: string;
}Usage Examples:
import { getLocal } from "mockttp";
const mockServer = getLocal({
https: {
keyLength: 2048
}
});
await mockServer.start();
// Monitor TLS issues
await mockServer.on('tls-client-error', (error) => {
console.log(`TLS error: ${error.failureCause}`);
if (error.tlsMetadata.sniHostname) {
console.log(`SNI hostname: ${error.tlsMetadata.sniHostname}`);
}
if (error.tlsMetadata.ja3Fingerprint) {
console.log(`JA3 fingerprint: ${error.tlsMetadata.ja3Fingerprint}`);
}
});
// Monitor client errors
await mockServer.on('client-error', (error) => {
console.log(`Client error: ${error.errorCode}`);
console.log(`Request: ${error.request.method} ${error.request.url}`);
if (error.response !== 'aborted') {
console.log(`Response: ${error.response.statusCode}`);
}
});
// Monitor TLS passthrough (if configured)
await mockServer.on('tls-passthrough-opened', (event) => {
console.log(`TLS passthrough opened to ${event.destination.hostname}:${event.destination.port}`);
});
await mockServer.on('tls-passthrough-closed', (event) => {
const duration = event.timingEvents.disconnectTimestamp! - event.timingEvents.connectTimestamp;
console.log(`TLS passthrough closed after ${duration}ms`);
});Monitor custom events emitted by rules during request processing.
interface Mockttp {
/**
* Subscribe to custom rule events.
* Rules may emit events with metadata about their processing.
*/
on<T = unknown>(event: 'rule-event', callback: (event: RuleEvent<T>) => void): Promise<void>;
}
interface RuleEvent<T = unknown> {
/**
* ID of the request being processed.
*/
requestId: string;
/**
* ID of the rule that emitted the event.
*/
ruleId: string;
/**
* Type of event (rule-specific).
*/
eventType: string;
/**
* Event data (rule-specific).
*/
eventData: T;
}Usage Examples:
import { getLocal } from "mockttp";
const mockServer = getLocal();
await mockServer.start();
// Monitor all rule events
await mockServer.on('rule-event', (event) => {
console.log(`Rule ${event.ruleId} emitted ${event.eventType} for request ${event.requestId}`);
console.log('Event data:', event.eventData);
});
// Rules that emit events include passthrough rules
await mockServer.forGet("/api/proxy")
.thenPassThrough();
// When requests hit this rule, it may emit events about upstream interactionsAll events include detailed timing information for performance analysis.
interface TimingEvents {
/**
* When the request/connection started (milliseconds since Unix epoch).
*/
startTime: number;
/**
* High-precision start timestamp (monotonic, for duration calculations).
*/
startTimestamp: number;
/**
* When the request body was fully received.
*/
bodyReceivedTimestamp?: number;
/**
* When response headers were sent.
*/
headersSentTimestamp?: number;
/**
* When the response was fully completed.
*/
responseSentTimestamp?: number;
/**
* When WebSocket was accepted (for WebSocket requests).
*/
wsAcceptedTimestamp?: number;
/**
* When WebSocket was closed (for WebSocket requests).
*/
wsClosedTimestamp?: number;
/**
* When the request/connection was aborted.
*/
abortedTimestamp?: number;
}
interface ConnectionTimingEvents {
startTime: number;
connectTimestamp: number;
tunnelTimestamp?: number;
disconnectTimestamp?: number;
}
interface TlsTimingEvents extends ConnectionTimingEvents {
handshakeTimestamp?: number;
}
interface TlsFailureTimingEvents extends TlsTimingEvents {
failureTimestamp: number;
}Common patterns for using events in testing:
Traffic Verification: Use events to verify that expected requests and responses occurred Performance Monitoring: Use timing events to measure request/response latencies Error Testing: Monitor abort and error events to test error handling WebSocket Testing: Track WebSocket message flow and connection lifecycle TLS Debugging: Monitor TLS events to debug certificate and handshake issues Custom Analytics: Collect custom metrics from rule events
Example Test Pattern:
import { getLocal } from "mockttp";
const mockServer = getLocal();
await mockServer.start();
const requestLog: string[] = [];
// Collect request log
await mockServer.on('request', (req) => {
requestLog.push(`${req.method} ${req.path}`);
});
// Set up mocks
await mockServer.forGet("/api/test").thenReply(200, "OK");
// Run your application tests...
// Verify expected requests occurred
expect(requestLog).toContain('GET /api/test');