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

event-handling.mddocs/

Event Handling

Comprehensive event system for monitoring and customizing proxy behavior throughout the request/response lifecycle using http-proxy events.

Capabilities

Event Configuration

Configure event handlers for various proxy lifecycle events through the options system.

/**
 * Event handler configuration for proxy lifecycle events
 */
interface OnProxyEvent<TReq = http.IncomingMessage, TRes = http.ServerResponse> {
  /** Error event handler for proxy errors */
  error?: httpProxy.ErrorCallback<Error, TReq, TRes>;
  
  /** Handler for outgoing proxy requests */
  proxyReq?: httpProxy.ProxyReqCallback<http.ClientRequest, TReq, TRes>;
  
  /** Handler for outgoing WebSocket proxy requests */
  proxyReqWs?: httpProxy.ProxyReqWsCallback<http.ClientRequest, TReq>;
  
  /** Handler for incoming proxy responses */
  proxyRes?: httpProxy.ProxyResCallback<TReq, TRes>;
  
  /** Handler for WebSocket connection open events */
  open?: httpProxy.OpenCallback;
  
  /** Handler for WebSocket connection close events */
  close?: httpProxy.CloseCallback<TReq>;
  
  /** Handler for proxy request start events */
  start?: httpProxy.StartCallback<TReq, TRes>;
  
  /** Handler for proxy request end events */
  end?: httpProxy.EndCallback<TReq, TRes>;
  
  /** Handler for connection reset errors */
  econnreset?: httpProxy.EconnresetCallback<Error, TReq, TRes>;
}

interface EventOptions<TReq, TRes> {
  /** Event handlers for proxy lifecycle events */
  on?: OnProxyEvent<TReq, TRes>;
}

Basic Event Handling:

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

const proxy = createProxyMiddleware({
  target: "http://api.example.com",
  on: {
    error: (err, req, res, target) => {
      console.error("Proxy error:", err.message);
    },
    
    proxyReq: (proxyReq, req, res) => {
      console.log("Sending request to:", proxyReq.path);
    },
    
    proxyRes: (proxyRes, req, res) => {
      console.log("Received response:", proxyRes.statusCode);
    },
  },
});

Error Event Handling

Handle proxy errors to provide custom error responses and logging.

/**
 * Error event callback for handling proxy errors
 * @param err - The error that occurred
 * @param req - The original client request
 * @param res - The response object to the client
 * @param target - The target URL (may be undefined for WebSocket errors)
 */
type ErrorCallback<Error, TReq, TRes> = (
  err: Error,
  req: TReq,
  res: TRes,
  target?: string | Partial<URL>
) => void;

Error Handling Examples:

// Comprehensive error handling
const errorHandlingProxy = createProxyMiddleware({
  target: "http://api.example.com",
  on: {
    error: (err, req, res, target) => {
      const errorInfo = {
        timestamp: new Date().toISOString(),
        error: err.message,
        code: err.code,
        url: req.url,
        method: req.method,
        target: target?.href || "unknown",
        headers: req.headers,
      };
      
      // Log detailed error information
      console.error("Proxy Error:", errorInfo);
      
      // Send custom error response if headers not sent
      if (!res.headersSent) {
        const statusCode = getStatusCodeFromError(err);
        
        res.writeHead(statusCode, {
          "Content-Type": "application/json",
          "X-Proxy-Error": "true",
          "X-Error-Code": err.code || "UNKNOWN",
        });
        
        res.end(JSON.stringify({
          error: {
            message: "Proxy request failed",
            code: statusCode,
            details: err.message,
            timestamp: errorInfo.timestamp,
            requestId: req.headers["x-request-id"],
          },
        }));
      }
      
      // Report to monitoring system
      reportError(errorInfo);
    },
  },
});

// Error mapping utility
function getStatusCodeFromError(err) {
  switch (err.code) {
    case "ENOTFOUND": return 502; // Bad Gateway
    case "ECONNREFUSED": return 503; // Service Unavailable
    case "ETIMEDOUT": return 504; // Gateway Timeout
    case "ECONNRESET": return 502; // Bad Gateway
    default: return 500; // Internal Server Error
  }
}

Request Event Handling

Monitor and modify outgoing proxy requests before they are sent to the target server.

/**
 * Proxy request event callback for outgoing HTTP requests
 * @param proxyReq - The outgoing request to the target server
 * @param req - The original client request
 * @param res - The response object to the client
 */
type ProxyReqCallback<TProxyReq, TReq, TRes> = (
  proxyReq: TProxyReq,
  req: TReq,
  res: TRes
) => void;

/**
 * Proxy request event callback for outgoing WebSocket requests
 * @param proxyReq - The outgoing WebSocket request
 * @param req - The original client request
 */
type ProxyReqWsCallback<TProxyReq, TReq> = (
  proxyReq: TProxyReq,
  req: TReq
) => void;

Request Modification Examples:

// Request header manipulation
const requestModifyProxy = createProxyMiddleware({
  target: "http://api.example.com",
  on: {
    proxyReq: (proxyReq, req, res) => {
      // Add custom headers
      proxyReq.setHeader("X-Forwarded-By", "my-proxy");
      proxyReq.setHeader("X-Request-ID", generateRequestId());
      proxyReq.setHeader("X-Client-IP", req.connection.remoteAddress);
      
      // Add authentication
      const apiKey = process.env.API_KEY;
      if (apiKey) {
        proxyReq.setHeader("Authorization", `Bearer ${apiKey}`);
      }
      
      // Modify user agent
      proxyReq.setHeader("User-Agent", "MyApp/1.0 (Proxy)");
      
      // Remove sensitive headers
      proxyReq.removeHeader("cookie");
      proxyReq.removeHeader("x-internal-token");
      
      // Log request details
      console.log(`β†’ ${req.method} ${req.url} -> ${proxyReq.host}${proxyReq.path}`);
    },
    
    // WebSocket request handling
    proxyReqWs: (proxyReq, req) => {
      // Add WebSocket-specific headers
      proxyReq.setHeader("X-WebSocket-Client", req.headers["user-agent"]);
      proxyReq.setHeader("X-Forwarded-Proto", req.connection.encrypted ? "wss" : "ws");
      
      console.log(`WebSocket connection: ${req.url}`);
    },
  },
});

// Request body modification (for POST/PUT requests)
const bodyModifyProxy = createProxyMiddleware({
  target: "http://api.example.com",
  on: {
    proxyReq: (proxyReq, req, res) => {
      // Handle request body modification for parsed bodies
      if (req.body && (req.method === "POST" || req.method === "PUT")) {
        // Modify the request body
        const modifiedBody = {
          ...req.body,
          timestamp: new Date().toISOString(),
          proxy: {
            version: "1.0",
            source: "my-proxy",
          },
        };
        
        const bodyData = JSON.stringify(modifiedBody);
        
        // Update headers
        proxyReq.setHeader("Content-Type", "application/json");
        proxyReq.setHeader("Content-Length", Buffer.byteLength(bodyData));
        
        // Write the modified body
        proxyReq.write(bodyData);
      }
    },
  },
});

Response Event Handling

Monitor and process responses from the target server before they reach the client.

/**
 * Proxy response event callback for incoming responses
 * @param proxyRes - The response from the target server
 * @param req - The original client request
 * @param res - The response object to the client
 */
type ProxyResCallback<TReq, TRes> = (
  proxyRes: http.IncomingMessage,
  req: TReq,
  res: TRes
) => void;

Response Processing Examples:

// Response monitoring and header modification
const responseMonitorProxy = createProxyMiddleware({
  target: "http://api.example.com",
  on: {
    proxyRes: (proxyRes, req, res) => {
      // Log response details
      const duration = Date.now() - req.startTime;
      console.log(`← ${req.method} ${req.url} [${proxyRes.statusCode}] ${duration}ms`);
      
      // Add custom response headers
      res.setHeader("X-Proxy-Server", "my-proxy/1.0");
      res.setHeader("X-Response-Time", `${duration}ms`);
      res.setHeader("X-Upstream-Server", proxyRes.headers["server"] || "unknown");
      
      // Security headers
      res.setHeader("X-Content-Type-Options", "nosniff");
      res.setHeader("X-Frame-Options", "DENY");
      
      // CORS headers (if needed)
      if (req.headers.origin) {
        res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
        res.setHeader("Access-Control-Allow-Credentials", "true");
      }
      
      // Handle specific status codes
      if (proxyRes.statusCode >= 400) {
        console.warn(`Error response: ${proxyRes.statusCode} for ${req.url}`);
        
        // Add error tracking header
        res.setHeader("X-Error-Tracked", "true");
        
        // Send error notification
        notifyError({
          statusCode: proxyRes.statusCode,
          url: req.url,
          method: req.method,
          timestamp: new Date().toISOString(),
        });
      }
      
      // Performance monitoring
      if (duration > 5000) { // Slow response (> 5 seconds)
        console.warn(`Slow response: ${duration}ms for ${req.url}`);
        res.setHeader("X-Slow-Response", "true");
      }
    },
  },
});

// Response caching and optimization
const responseCacheProxy = createProxyMiddleware({
  target: "http://api.example.com",
  on: {
    proxyRes: (proxyRes, req, res) => {
      const contentType = proxyRes.headers["content-type"];
      const cacheControl = proxyRes.headers["cache-control"];
      
      // Add caching headers for static content
      if (contentType?.includes("application/json") && proxyRes.statusCode === 200) {
        if (!cacheControl) {
          res.setHeader("Cache-Control", "public, max-age=300"); // 5 minutes
        }
        
        // Add ETag for caching
        const etag = generateETag(req.url, proxyRes.headers);
        res.setHeader("ETag", etag);
      }
      
      // Compress responses if possible
      const acceptEncoding = req.headers["accept-encoding"];
      if (acceptEncoding?.includes("gzip") && shouldCompress(contentType)) {
        res.setHeader("Content-Encoding", "gzip");
      }
      
      // Track response metrics
      trackResponseMetrics({
        url: req.url,
        method: req.method,
        statusCode: proxyRes.statusCode,
        contentType,
        contentLength: proxyRes.headers["content-length"],
        duration: Date.now() - req.startTime,
      });
    },
  },
});

WebSocket Event Handling

Handle WebSocket connection lifecycle events for real-time applications.

/**
 * WebSocket open event callback
 * @param proxySocket - The WebSocket connection to the target
 */
type OpenCallback = (proxySocket: net.Socket) => void;

/**
 * WebSocket close event callback
 * @param req - The original WebSocket upgrade request
 * @param proxySocket - The WebSocket connection to the target  
 * @param proxyHead - The first packet of the tunneling stream
 */
type CloseCallback<TReq> = (
  req: TReq,
  proxySocket: net.Socket,
  proxyHead: Buffer
) => void;

WebSocket Event Examples:

// WebSocket connection monitoring
const wsMonitorProxy = createProxyMiddleware({
  target: "ws://websocket.example.com",
  ws: true,
  on: {
    open: (proxySocket) => {
      const address = proxySocket.address();
      console.log(`WebSocket connected to ${address.address}:${address.port}`);
      
      // Track active connections
      activeConnections.add(proxySocket);
      
      // Set up connection monitoring
      proxySocket.on("data", (data) => {
        trackWebSocketData("outbound", data.length);
      });
      
      proxySocket.on("error", (error) => {
        console.error("WebSocket proxy error:", error);
        activeConnections.delete(proxySocket);
      });
    },
    
    close: (req, proxySocket, proxyHead) => {
      const address = proxySocket.address();
      console.log(`WebSocket disconnected from ${address?.address}:${address?.port}`);
      
      // Clean up connection tracking
      activeConnections.delete(proxySocket);
      
      // Log connection duration
      const duration = Date.now() - req.connectionStartTime;
      console.log(`WebSocket session duration: ${duration}ms`);
    },
    
    // Handle WebSocket proxy requests
    proxyReqWs: (proxyReq, req) => {
      req.connectionStartTime = Date.now();
      console.log(`WebSocket upgrade request: ${req.url}`);
      
      // Add custom WebSocket headers
      proxyReq.setHeader("X-WebSocket-Proxy", "my-proxy/1.0");
      
      // Authentication for WebSocket connections
      const token = req.headers["sec-websocket-protocol"];
      if (token) {
        proxyReq.setHeader("Authorization", `Bearer ${token}`);
      }
    },
  },
});

Lifecycle Event Handling

Handle proxy request start and end events for comprehensive request tracking.

/**
 * Proxy request start event callback
 * @param req - The original client request
 * @param res - The response object to the client
 * @param target - The target URL
 */
type StartCallback<TReq, TRes> = (
  req: TReq,
  res: TRes,
  target: string | Partial<URL>
) => void;

/**
 * Proxy request end event callback
 * @param req - The original client request
 * @param res - The response object to the client
 * @param proxyRes - The response from the target server
 */
type EndCallback<TReq, TRes> = (
  req: TReq,
  res: TRes,
  proxyRes: http.IncomingMessage
) => void;

Lifecycle Event Examples:

// Comprehensive request lifecycle tracking
const lifecycleTrackingProxy = createProxyMiddleware({
  target: "http://api.example.com",
  on: {
    start: (req, res, target) => {
      // Initialize request tracking
      req.startTime = Date.now();
      req.requestId = generateRequestId();
      
      console.log(`πŸš€ Starting request ${req.requestId}: ${req.method} ${req.url} -> ${target.href}`);
      
      // Add request to active tracking
      activeRequests.set(req.requestId, {
        method: req.method,
        url: req.url,
        target: target.href,
        startTime: req.startTime,
        clientIP: req.connection.remoteAddress,
        userAgent: req.headers["user-agent"],
      });
    },
    
    end: (req, res, proxyRes) => {
      const duration = Date.now() - req.startTime;
      const statusCode = proxyRes.statusCode;
      const contentLength = proxyRes.headers["content-length"];
      
      console.log(`βœ… Completed request ${req.requestId}: [${statusCode}] ${duration}ms ${contentLength || 0} bytes`);
      
      // Update request tracking
      const requestData = activeRequests.get(req.requestId);
      if (requestData) {
        requestData.endTime = Date.now();
        requestData.duration = duration;
        requestData.statusCode = statusCode;
        requestData.contentLength = contentLength;
        
        // Move to completed requests log
        completedRequests.push(requestData);
        activeRequests.delete(req.requestId);
        
        // Maintain completed requests history (keep last 1000)
        if (completedRequests.length > 1000) {
          completedRequests.shift();
        }
      }
      
      // Performance alerting
      if (duration > 10000) { // > 10 seconds
        console.warn(`⚠️  Slow request detected: ${req.requestId} took ${duration}ms`);
        alertSlowRequest(requestData);
      }
      
      if (statusCode >= 500) {
        console.error(`❌ Server error: ${req.requestId} returned ${statusCode}`);
        alertServerError(requestData);
      }
    },
    
    econnreset: (err, req, res, target) => {
      console.error(`πŸ”Œ Connection reset: ${req.requestId || 'unknown'} - ${err.message}`);
      
      // Clean up tracking
      if (req.requestId) {
        activeRequests.delete(req.requestId);
      }
      
      // Track connection reset events
      connectionResets.push({
        timestamp: new Date().toISOString(),
        error: err.message,
        url: req.url,
        target: target?.href,
      });
    },
  },
});