or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

event-handling.mdindex.mdlegacy-api.mdpath-filtering-routing.mdplugin-system.mdproxy-creation.mdresponse-handling.md
tile.json

plugin-system.mddocs/

Plugin System

Extensible plugin architecture for customizing proxy server behavior and adding additional functionality through the underlying http-proxy server instance.

Capabilities

Plugin Interface

Core plugin system allowing direct access to the http-proxy server instance for advanced customization.

/**
 * Plugin function interface for extending proxy server functionality
 * @param proxyServer - The underlying http-proxy server instance
 * @param options - The proxy middleware options
 */
interface Plugin<TReq = http.IncomingMessage, TRes = http.ServerResponse> {
  (proxyServer: httpProxy<TReq, TRes>, options: Options<TReq, TRes>): void;
}

interface PluginOptions<TReq, TRes> {
  /** Array of plugin functions to apply to the proxy server */
  plugins?: Plugin<TReq, TRes>[];
  
  /** Whether to eject built-in default plugins (default: false) */
  ejectPlugins?: boolean;
}

Plugins receive access to the http-proxy server instance and can:

  • Subscribe to proxy lifecycle events
  • Modify proxy behavior
  • Add custom error handling
  • Implement logging and monitoring
  • Extend functionality with custom logic

Basic Plugin Usage:

import { createProxyMiddleware } from "http-proxy-middleware";

// Custom logging plugin
const customLoggingPlugin = (proxyServer, options) => {
  proxyServer.on("proxyReq", (proxyReq, req, res) => {
    console.log(`→ ${req.method} ${req.url} -> ${proxyReq.path}`);
  });
  
  proxyServer.on("proxyRes", (proxyRes, req, res) => {
    console.log(`← ${req.method} ${req.url} [${proxyRes.statusCode}]`);
  });
};

// Custom error handling plugin
const errorHandlingPlugin = (proxyServer, options) => {
  proxyServer.on("error", (error, req, res, target) => {
    // Custom error logging
    console.error("Proxy error:", {
      url: req.url,
      method: req.method,
      target: target?.href,
      error: error.message,
    });
    
    // Custom error response
    if (!res.headersSent) {
      res.writeHead(502, { "Content-Type": "application/json" });
      res.end(JSON.stringify({
        error: "Proxy Error",
        message: "Failed to connect to upstream server",
        timestamp: new Date().toISOString(),
      }));
    }
  });
};

// Using plugins
const proxy = createProxyMiddleware({
  target: "http://api.example.com",
  plugins: [customLoggingPlugin, errorHandlingPlugin],
});

Built-in Default Plugins

Pre-configured plugins that are automatically included unless ejected, providing essential proxy functionality.

/**
 * Built-in plugin for debugging proxy errors and preventing server crashes
 */
const debugProxyErrorsPlugin: Plugin;

/**
 * Built-in plugin for handling error responses with proper status codes
 */
const errorResponsePlugin: Plugin;

/**
 * Built-in plugin for logging request/response exchanges and WebSocket connections
 */
const loggerPlugin: Plugin;

/**
 * Built-in plugin that implements the options.on event handler system
 */
const proxyEventsPlugin: Plugin;

Default Plugin Control:

// Using default plugins (automatic)
const withDefaultPlugins = createProxyMiddleware({
  target: "http://api.example.com",
  // Default plugins are automatically included
});

// Ejecting default plugins and using custom ones only
const customOnlyProxy = createProxyMiddleware({
  target: "http://api.example.com",
  ejectPlugins: true, // Remove all default plugins
  plugins: [
    customLoggingPlugin,
    customErrorHandlingPlugin,
    customMetricsPlugin,
  ],
});

// Combining default and custom plugins
const combinedProxy = createProxyMiddleware({
  target: "http://api.example.com",
  plugins: [
    // Custom plugins are added to default plugins
    customMetricsPlugin,
    customCachingPlugin,
  ],
});

Debug Proxy Errors Plugin

Built-in plugin that handles http-proxy errors to prevent server crashes and provides debugging information.

/**
 * Subscribe to http-proxy error events to prevent server from crashing.
 * Errors are logged with debug library.
 * 
 * Handles:
 * - General proxy errors
 * - Socket errors in proxyReq events
 * - SSE connection cleanup
 * - WebSocket errors
 * - Connection reset errors
 */
const debugProxyErrorsPlugin: Plugin;

This plugin automatically:

  • Prevents uncaught proxy errors from crashing the server
  • Logs detailed error information using the debug library
  • Handles socket errors for both HTTP and WebSocket connections
  • Cleans up Server-Sent Events (SSE) connections properly
  • Manages connection reset scenarios

Error Response Plugin

Built-in plugin that provides proper error responses when proxy errors occur.

/**
 * Handles proxy errors by sending appropriate HTTP error responses
 * to clients instead of leaving connections hanging.
 * 
 * Features:
 * - Proper HTTP status code mapping
 * - Error message generation
 * - Header management
 * - Socket cleanup for WebSocket errors
 */
const errorResponsePlugin: Plugin;

This plugin:

  • Maps error codes to appropriate HTTP status codes
  • Sends informative error messages to clients
  • Handles both HTTP and WebSocket error scenarios
  • Ensures proper connection cleanup

Logger Plugin

Built-in plugin that provides comprehensive logging for proxy operations.

/**
 * Logs proxy operations including request/response exchanges,
 * WebSocket connections, and error scenarios.
 * 
 * Supports:
 * - Request/response logging with timing
 * - WebSocket connection tracking
 * - Error logging with context
 * - Compatible with Express, BrowserSync, Next.js request formats
 */
const loggerPlugin: Plugin;

The logger plugin provides:

  • HTTP request/response logging with status codes
  • WebSocket connection open/close tracking
  • Error logging with full context
  • Support for different framework request objects (Express, BrowserSync, etc.)

Custom Plugin Examples

Advanced examples of custom plugins for specific use cases.

Metrics and Monitoring Plugin:

// Metrics collection plugin
const metricsPlugin = (proxyServer, options) => {
  const metrics = {
    requests: 0,
    responses: { success: 0, error: 0 },
    responseTime: [],
    errors: [],
  };
  
  proxyServer.on("proxyReq", (proxyReq, req, res) => {
    metrics.requests++;
    req.startTime = Date.now();
  });
  
  proxyServer.on("proxyRes", (proxyRes, req, res) => {
    const responseTime = Date.now() - req.startTime;
    metrics.responseTime.push(responseTime);
    
    if (proxyRes.statusCode < 400) {
      metrics.responses.success++;
    } else {
      metrics.responses.error++;
    }
    
    // Keep only last 1000 response times
    if (metrics.responseTime.length > 1000) {
      metrics.responseTime.shift();
    }
  });
  
  proxyServer.on("error", (error, req, res, target) => {
    metrics.errors.push({
      timestamp: new Date().toISOString(),
      error: error.message,
      url: req.url,
      target: target?.href,
    });
    
    // Keep only last 100 errors
    if (metrics.errors.length > 100) {
      metrics.errors.shift();
    }
  });
  
  // Expose metrics endpoint
  const getMetrics = () => ({
    ...metrics,
    averageResponseTime: metrics.responseTime.length > 0 
      ? metrics.responseTime.reduce((a, b) => a + b, 0) / metrics.responseTime.length 
      : 0,
  });
  
  // Store metrics accessor on proxy server for external access
  proxyServer.getMetrics = getMetrics;
};

// Usage with metrics endpoint
const proxy = createProxyMiddleware({
  target: "http://api.example.com",
  plugins: [metricsPlugin],
});

// Access metrics
app.get("/proxy-metrics", (req, res) => {
  res.json(proxy.getMetrics());
});

Circuit Breaker Plugin:

// Circuit breaker plugin for fault tolerance
const circuitBreakerPlugin = (proxyServer, options) => {
  const circuitBreaker = {
    state: "CLOSED", // CLOSED, OPEN, HALF_OPEN
    failures: 0,
    lastFailureTime: null,
    threshold: 5, // Failures before opening
    timeout: 60000, // Time before attempting to close (1 minute) 
  };
  
  const isCircuitOpen = () => {
    if (circuitBreaker.state === "OPEN") {
      const now = Date.now();
      if (now - circuitBreaker.lastFailureTime > circuitBreaker.timeout) {
        circuitBreaker.state = "HALF_OPEN";
        return false;
      }
      return true;
    }
    return false;
  };
  
  proxyServer.on("proxyReq", (proxyReq, req, res) => {
    if (isCircuitOpen()) {
      // Circuit is open, reject request immediately
      proxyReq.destroy();
      res.writeHead(503, { "Content-Type": "application/json" });
      res.end(JSON.stringify({
        error: "Service Unavailable",
        message: "Circuit breaker is open",
        retryAfter: Math.ceil((circuitBreaker.timeout - (Date.now() - circuitBreaker.lastFailureTime)) / 1000),
      }));
    }
  });
  
  proxyServer.on("proxyRes", (proxyRes, req, res) => {
    if (proxyRes.statusCode < 500) {
      // Success response, reset failure count
      if (circuitBreaker.state === "HALF_OPEN") {
        circuitBreaker.state = "CLOSED";
        circuitBreaker.failures = 0;
      }
    }
  });
  
  proxyServer.on("error", (error, req, res, target) => {
    circuitBreaker.failures++;
    circuitBreaker.lastFailureTime = Date.now();
    
    if (circuitBreaker.failures >= circuitBreaker.threshold) {
      circuitBreaker.state = "OPEN";
      console.log(`Circuit breaker opened after ${circuitBreaker.failures} failures`);
    }
  });
};

Authentication Plugin:

// Authentication and authorization plugin
const authPlugin = (proxyServer, options) => {
  proxyServer.on("proxyReq", async (proxyReq, req, res) => {
    const authHeader = req.headers.authorization;
    
    if (!authHeader || !authHeader.startsWith("Bearer ")) {
      proxyReq.destroy();
      res.writeHead(401, { "Content-Type": "application/json" });
      res.end(JSON.stringify({
        error: "Unauthorized",
        message: "Bearer token required",
      }));
      return;
    }
    
    const token = authHeader.substring(7);
    
    try {
      // Validate token (implement your validation logic)
      const user = await validateToken(token);
      
      // Add user information to forwarded request
      proxyReq.setHeader("X-User-ID", user.id);
      proxyReq.setHeader("X-User-Role", user.role);
      proxyReq.setHeader("X-User-Permissions", JSON.stringify(user.permissions));
      
      // Remove original auth header for security
      proxyReq.removeHeader("authorization");
      
    } catch (error) {
      proxyReq.destroy();
      res.writeHead(403, { "Content-Type": "application/json" });
      res.end(JSON.stringify({
        error: "Forbidden", 
        message: "Invalid or expired token",
      }));
    }
  });
};

Rate Limiting Plugin:

// Rate limiting plugin
const rateLimitPlugin = (proxyServer, options) => {
  const rateLimits = new Map(); // IP -> { count, resetTime }
  const RATE_LIMIT = 100; // requests per minute
  const WINDOW_SIZE = 60000; // 1 minute in milliseconds
  
  proxyServer.on("proxyReq", (proxyReq, req, res) => {
    const clientIP = req.connection.remoteAddress || req.headers["x-forwarded-for"];
    const now = Date.now();
    
    let rateLimitData = rateLimits.get(clientIP);
    
    if (!rateLimitData || now > rateLimitData.resetTime) {
      // New window
      rateLimitData = {
        count: 1,
        resetTime: now + WINDOW_SIZE,
      };
    } else {
      // Existing window
      rateLimitData.count++;
    }
    
    rateLimits.set(clientIP, rateLimitData);
    
    if (rateLimitData.count > RATE_LIMIT) {
      // Rate limit exceeded
      proxyReq.destroy();
      
      const retryAfter = Math.ceil((rateLimitData.resetTime - now) / 1000);
      
      res.writeHead(429, {
        "Content-Type": "application/json",
        "Retry-After": retryAfter,
        "X-RateLimit-Limit": RATE_LIMIT,
        "X-RateLimit-Remaining": 0,
        "X-RateLimit-Reset": rateLimitData.resetTime,
      });
      
      res.end(JSON.stringify({
        error: "Too Many Requests",
        message: `Rate limit of ${RATE_LIMIT} requests per minute exceeded`,
        retryAfter,
      }));
    } else {
      // Add rate limit headers to successful requests
      proxyReq.setHeader("X-RateLimit-Limit", RATE_LIMIT);
      proxyReq.setHeader("X-RateLimit-Remaining", RATE_LIMIT - rateLimitData.count);
      proxyReq.setHeader("X-RateLimit-Reset", rateLimitData.resetTime);
    }
  });
  
  // Cleanup old entries periodically
  setInterval(() => {
    const now = Date.now();
    for (const [ip, data] of rateLimits.entries()) {
      if (now > data.resetTime) {
        rateLimits.delete(ip);
      }
    }
  }, WINDOW_SIZE);
};