CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-mockttp

Mock HTTP server for testing HTTP clients and stubbing webservices

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

event-monitoring.mddocs/

Event Monitoring

Comprehensive event system for monitoring all HTTP/WebSocket traffic, TLS connections, errors, and custom rule events to enable detailed testing and debugging.

Capabilities

HTTP Request/Response Events

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");

WebSocket Events

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();

TLS and Connection Events

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`);
});

Custom Rule Events

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 interactions

Event Timing Information

All 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;
}

Event-Based Testing Patterns

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');

docs

certificate-management.md

event-monitoring.md

http-request-mocking.md

index.md

mock-server-setup.md

response-actions.md

websocket-mocking.md

tile.json