CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-replay

HTTP request recording and replay system for API testing

Pending
Overview
Eval results
Files

proxy-system.mddocs/

Proxy System

Replay's extensible proxy system allows custom request processing through a configurable chain of handlers. The system processes HTTP requests through a series of proxies until one provides a response.

Capabilities

Use Method

Add custom proxy handlers to the processing chain.

/**
 * Add a proxy handler to the beginning of the processing chain
 * @param {ProxyHandler} proxy - Handler function to process requests
 * @returns {Replay} The Replay instance for chaining
 */
Replay.use(proxy: ProxyHandler): Replay;

/**
 * Proxy handler function type
 * @typedef {Function} ProxyHandler
 * @param {any} request - The HTTP request object
 * @param {Function} callback - Callback function to call with response or error
 */
type ProxyHandler = (request: any, callback: (error?: Error, response?: any) => void) => void;

Usage Examples:

const Replay = require('replay');

// Custom authentication proxy
Replay.use(function authProxy(request, callback) {
  if (request.url.hostname === 'api.example.com') {
    // Add authentication headers
    request.headers['Authorization'] = 'Bearer ' + process.env.API_TOKEN;
  }
  // Pass to next proxy in chain
  callback();
});

// Custom response modification proxy
Replay.use(function responseModifier(request, callback) {
  if (request.url.pathname === '/api/time') {
    // Provide mock response
    callback(null, {
      statusCode: 200,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ timestamp: Date.now() })
    });
    return;
  }
  // Pass to next proxy
  callback();
});

// Chain multiple proxies
Replay
  .use(customLoggingProxy)
  .use(authenticationProxy)
  .use(mockDataProxy);

Built-in Logger Proxy

Replay provides a built-in logging proxy for debugging HTTP requests.

/**
 * Create a logging proxy that logs HTTP requests when DEBUG=replay
 * @returns {ProxyHandler} Logging proxy handler function
 */
Replay.logger(): ProxyHandler;

Usage Examples:

const Replay = require('replay');

// Add logging to the proxy chain
Replay.use(Replay.logger());

// Logger output when DEBUG=replay is set:
// Requesting GET http://api.example.com:80/users
// Requesting GET https://cdn.example.com:443/assets/style.css

Environment Configuration:

# Enable debug logging
DEBUG=replay node app.js

# Logger will output request URLs:
# Requesting GET http://api.example.com:80/data
# Requesting GET https://auth.example.com:443/login

Chain API

Direct access to the internal proxy chain for advanced management.

/**
 * Append a handler to the end of the proxy chain (executed last)
 * @param {ProxyHandler} handler - Handler function to append
 * @returns {Chain} The Chain instance for chaining
 */
Replay.chain.append(handler: ProxyHandler): Chain;

/**
 * Prepend a handler to the beginning of the proxy chain (executed first)
 * @param {ProxyHandler} handler - Handler function to prepend
 * @returns {Chain} The Chain instance for chaining
 */
Replay.chain.prepend(handler: ProxyHandler): Chain;

/**
 * Clear all handlers from the proxy chain
 */
Replay.chain.clear(): void;

/**
 * Get the first handler in the proxy chain
 * @type {ProxyHandler | null}
 */
Replay.chain.start: ProxyHandler | null;

Usage Examples:

const Replay = require('replay');

// Direct chain manipulation
Replay.chain.prepend(function firstHandler(request, callback) {
  console.log('First handler - always executes first');
  callback(); // Pass to next handler
});

Replay.chain.append(function lastHandler(request, callback) {
  console.log('Last handler - executes if no other handler provides response');
  callback(); // Pass to next handler
});

// Get the first handler
const firstHandler = Replay.chain.start;
if (firstHandler) {
  console.log('Chain has handlers');
}

// Clear all handlers (advanced usage - removes built-in functionality)
// Note: This will disable all replay functionality including recording/playback
Replay.chain.clear();

// Chain operations can be chained together
Replay.chain
  .prepend(authHandler)
  .prepend(loggingHandler)
  .append(fallbackHandler);

Note: Direct chain manipulation is advanced functionality. The Replay.use() method is recommended for most use cases as it maintains the proper order with built-in handlers.

Proxy Chain Architecture

Processing Flow

  1. Request Capture: HTTP requests are intercepted by Replay's patched HTTP modules
  2. Chain Execution: Request passes through proxy chain from first to last
  3. Response Handling: First proxy to provide a response terminates the chain
  4. Fallback: If no proxy provides a response, request fails or passes through based on mode

Default Proxy Chain

Replay sets up a default proxy chain:

// Default chain (from first to last):
Replay
  .use(passThrough(passWhenBloodyOrCheat))  // Pass-through for bloody/cheat modes
  .use(recorder(replay))                     // Record/replay functionality
  .use(logger(replay))                       // Debug logging (when enabled)
  .use(passThrough(passToLocalhost));       // Pass-through for localhost

Custom Proxy Implementation

/**
 * Example custom proxy implementation
 */
function customProxy(request, callback) {
  // Check if this proxy should handle the request
  if (shouldHandle(request)) {
    // Process the request
    processRequest(request, function(error, response) {
      if (error) {
        // Pass error to callback
        callback(error);
      } else {
        // Provide response (terminates chain)
        callback(null, response);
      }
    });
  } else {
    // Pass to next proxy in chain (no arguments = continue)
    callback();
  }
}

function shouldHandle(request) {
  return request.url.hostname === 'mock.example.com';
}

function processRequest(request, callback) {
  // Custom request processing logic
  const response = {
    statusCode: 200,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ mocked: true })
  };
  callback(null, response);
}

// Add to proxy chain
Replay.use(customProxy);

Advanced Proxy Patterns

Conditional Processing

// Proxy that only handles specific hosts
Replay.use(function conditionalProxy(request, callback) {
  const hostname = request.url.hostname;
  
  if (hostname === 'api.example.com') {
    // Handle API requests specially
    handleApiRequest(request, callback);
  } else if (hostname.endsWith('.amazonaws.com')) {
    // Handle AWS requests
    handleAwsRequest(request, callback);
  } else {
    // Pass to next proxy
    callback();
  }
});

Response Transformation

// Proxy that transforms responses
Replay.use(function responseTransformer(request, callback) {
  if (request.url.pathname.startsWith('/api/v1/')) {
    // Let other proxies handle the request first
    callback();
  } else {
    callback();
  }
});

// Note: Response transformation typically requires wrapping other proxies
// or intercepting at a different level in the HTTP stack

Error Handling

// Proxy with comprehensive error handling
Replay.use(function errorHandlingProxy(request, callback) {
  try {
    if (request.url.hostname === 'flaky-api.example.com') {
      // Simulate flaky service
      if (Math.random() < 0.3) {
        const error = new Error('Service temporarily unavailable');
        error.code = 'ECONNREFUSED';
        callback(error);
        return;
      }
    }
    
    // Pass to next proxy
    callback();
  } catch (error) {
    callback(error);
  }
});

Request Modification

// Proxy that modifies requests
Replay.use(function requestModifier(request, callback) {
  // Add custom headers
  request.headers['X-Client-Version'] = '1.2.3';
  request.headers['X-Request-ID'] = generateRequestId();
  
  // Modify URL for testing
  if (process.env.NODE_ENV === 'test') {
    request.url.hostname = request.url.hostname.replace('.com', '.test');
  }
  
  // Pass modified request to next proxy
  callback();
});

function generateRequestId() {
  return Math.random().toString(36).substr(2, 9);
}

Proxy Chain Management

Order Importance

Proxies are processed in the order they are added with use(). Later proxies are added to the beginning of the chain:

Replay.use(proxyA);  // Will execute third
Replay.use(proxyB);  // Will execute second  
Replay.use(proxyC);  // Will execute first

// Execution order: proxyC -> proxyB -> proxyA -> default chain

Chaining Support

The use() method returns the Replay instance for method chaining:

Replay
  .use(authenticationProxy)
  .use(loggingProxy)
  .use(mockDataProxy)
  .passThrough('*.amazonaws.com')
  .drop('*.doubleclick.net');

Integration with Built-in Proxies

Custom proxies work alongside Replay's built-in functionality:

// Custom proxy + built-in features
Replay
  .use(customAuthProxy)        // Custom authentication
  .use(Replay.logger())        // Built-in logging
  .passThrough('cdn.example.com') // Built-in pass-through
  .localhost('*.local');       // Built-in localhost routing

Install with Tessl CLI

npx tessl i tessl/npm-replay

docs

fixture-management.md

host-configuration.md

index.md

mode-management.md

proxy-system.md

tile.json