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

response-handling.mddocs/

Response Handling

Response interception and manipulation capabilities for modifying upstream responses before they reach the client.

Capabilities

Response Interceptor

Intercept and modify responses from upstream servers with automatic decompression support for common compression formats.

/**
 * Creates a response interceptor function for modifying upstream responses
 * @param interceptor - Function that receives the response buffer and can modify it
 * @returns Response handler function for use with selfHandleResponse option
 */
function responseInterceptor<
  TReq extends http.IncomingMessage = http.IncomingMessage,
  TRes extends http.ServerResponse = http.ServerResponse,
>(interceptor: Interceptor<TReq, TRes>): (proxyRes: TReq, req: TReq, res: TRes) => Promise<void>;

/**
 * Interceptor function interface for processing response buffers
 */
type Interceptor<TReq = http.IncomingMessage, TRes = http.ServerResponse> = (
  buffer: Buffer,
  proxyRes: TReq,
  req: TReq,
  res: TRes,
) => Promise<Buffer | string>;

The response interceptor automatically handles:

  • Automatic decompression of gzip, deflate, and brotli compressed responses
  • Content-length recalculation after modifications
  • Header preservation from the original response
  • Buffer concatenation for streamed responses

Basic Response Interception Examples:

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

// Basic JSON response modification
const jsonModifierProxy = createProxyMiddleware({
  target: "http://api.example.com",
  selfHandleResponse: true, // Required for response interception
  on: {
    proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
      // Parse JSON response
      const response = JSON.parse(responseBuffer.toString("utf8"));
      
      // Add metadata to all responses
      response._metadata = {
        proxiedAt: new Date().toISOString(),
        originalHost: proxyRes.headers?.host,
        requestId: req.headers["x-request-id"],
      };
      
      // Return modified JSON
      return JSON.stringify(response);
    }),
  },
});

// HTML content modification
const htmlModifierProxy = createProxyMiddleware({
  target: "http://web.example.com",
  selfHandleResponse: true,
  on: {
    proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
      const contentType = proxyRes.headers?.["content-type"];
      
      // Only modify HTML responses
      if (contentType?.includes("text/html")) {
        let html = responseBuffer.toString("utf8");
        
        // Inject custom analytics script
        const analyticsScript = '<script src="/analytics.js"></script>';
        html = html.replace("</head>", `${analyticsScript}</head>`);
        
        // Add custom header
        html = html.replace("<body>", '<body><div class="proxy-notice">Content proxied</div>');
        
        return html;
      }
      
      // Return unmodified for non-HTML content
      return responseBuffer;
    }),
  },
});

Advanced Response Processing

Complex response manipulation scenarios including conditional processing, format transformations, and multi-step processing.

Advanced Processing Examples:

// API response transformation and caching
const apiTransformProxy = createProxyMiddleware({
  target: "http://legacy-api.example.com",
  selfHandleResponse: true,
  on: {
    proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
      const contentType = proxyRes.headers?.["content-type"];
      const statusCode = proxyRes.statusCode;
      
      // Only process successful JSON responses
      if (statusCode === 200 && contentType?.includes("application/json")) {
        const data = JSON.parse(responseBuffer.toString("utf8"));
        
        // Transform legacy API format to modern format
        const transformedData = {
          success: true,
          data: transformLegacyFormat(data),
          pagination: data.pagination ? {
            page: data.pagination.current_page,
            totalPages: data.pagination.total_pages,
            totalItems: data.pagination.total_items,
            hasNext: data.pagination.has_next,
            hasPrev: data.pagination.has_prev,
          } : undefined,
          meta: {
            apiVersion: "v2",
            transformedAt: new Date().toISOString(),
            originalFormat: "legacy",
          },
        };
        
        // Cache the transformed response
        await cacheResponse(req.url, transformedData);
        
        // Set modern content type
        res.setHeader("Content-Type", "application/json; charset=utf-8");
        res.setHeader("X-API-Version", "v2");
        res.setHeader("X-Transformed", "true");
        
        return JSON.stringify(transformedData);
      }
      
      return responseBuffer;
    }),
  },
});

// Multi-format response handling with error transformation  
const multiFormatProxy = createProxyMiddleware({
  target: "http://api.example.com",
  selfHandleResponse: true,
  on: {
    proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
      const contentType = proxyRes.headers?.["content-type"];
      const statusCode = proxyRes.statusCode || 200;
      const accept = req.headers.accept;
      
      let responseData;
      
      // Parse response based on content type
      if (contentType?.includes("application/json")) {
        responseData = JSON.parse(responseBuffer.toString("utf8"));
      } else if (contentType?.includes("application/xml")) {
        responseData = await parseXML(responseBuffer.toString("utf8"));
      } else {
        responseData = { raw: responseBuffer.toString("utf8") };
      }
      
      // Handle error responses consistently
      if (statusCode >= 400) {
        const errorResponse = {
          error: {
            code: statusCode,
            message: responseData.message || responseData.error || "Unknown error",
            details: responseData.details || responseData,
            timestamp: new Date().toISOString(),
            path: req.url,
            method: req.method,
          },
        };
        
        res.setHeader("Content-Type", "application/json");
        return JSON.stringify(errorResponse);
      }
      
      // Transform response based on client preferences
      if (accept?.includes("application/xml") && !contentType?.includes("xml")) {
        // Convert JSON to XML if client prefers XML
        const xmlResponse = convertToXML(responseData);
        res.setHeader("Content-Type", "application/xml");
        return xmlResponse;
      }
      
      if (accept?.includes("text/csv") && Array.isArray(responseData)) {
        // Convert array data to CSV
        const csvResponse = convertToCSV(responseData);
        res.setHeader("Content-Type", "text/csv");
        res.setHeader("Content-Disposition", 'attachment; filename="data.csv"');
        return csvResponse;
      }
      
      // Default JSON response
      res.setHeader("Content-Type", "application/json");
      return JSON.stringify(responseData);
    }),
  },
});

Response Security and Filtering

Security-focused response processing including content sanitization, sensitive data filtering, and security header injection.

Security Processing Examples:

// Sensitive data filtering and security headers
const securityFilterProxy = createProxyMiddleware({
  target: "http://internal-api.example.com",
  selfHandleResponse: true,
  on: {
    proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
      const contentType = proxyRes.headers?.["content-type"];
      
      // Add security headers
      res.setHeader("X-Content-Type-Options", "nosniff");
      res.setHeader("X-Frame-Options", "DENY");
      res.setHeader("X-XSS-Protection", "1; mode=block");
      res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
      
      if (contentType?.includes("application/json")) {
        const data = JSON.parse(responseBuffer.toString("utf8"));
        
        // Remove sensitive fields based on user permissions
        const userRole = req.headers["x-user-role"];
        const sanitizedData = sanitizeResponse(data, userRole);
        
        // Remove potentially sensitive patterns
        const responseStr = JSON.stringify(sanitizedData);
        const cleanResponse = responseStr
          .replace(/\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g, "****-****-****-****") // Credit cards
          .replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, "[email protected]") // Email addresses
          .replace(/\b\d{3}-\d{2}-\d{4}\b/g, "***-**-****"); // SSN pattern
        
        return cleanResponse;
      }
      
      // Sanitize HTML content
      if (contentType?.includes("text/html")) {
        const html = responseBuffer.toString("utf8");
        // Remove potentially dangerous script tags and attributes
        const sanitizedHtml = sanitizeHtml(html, {
          allowedTags: ['p', 'div', 'span', 'a', 'strong', 'em', 'ul', 'ol', 'li'],
          allowedAttributes: {
            a: ['href', 'title'],
            div: ['class'],
            span: ['class'],
          },
        });
        
        return sanitizedHtml;
      }
      
      return responseBuffer;
    }),
  },
});

// Response validation and schema enforcement
const validationProxy = createProxyMiddleware({
  target: "http://api.example.com",
  selfHandleResponse: true,
  on: {
    proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
      const contentType = proxyRes.headers?.["content-type"];
      const statusCode = proxyRes.statusCode || 200;
      
      if (statusCode === 200 && contentType?.includes("application/json")) {
        const data = JSON.parse(responseBuffer.toString("utf8"));
        
        // Validate response against expected schema
        const endpoint = req.url?.split("?")[0];
        const schema = getResponseSchema(endpoint);
        
        if (schema) {
          const validation = validateResponse(data, schema);
          
          if (!validation.valid) {
            // Log validation errors
            console.error("Response validation failed:", validation.errors);
            
            // Return error response
            res.statusCode = 502;
            res.setHeader("Content-Type", "application/json");
            return JSON.stringify({
              error: {
                code: 502,
                message: "Invalid response format from upstream server",
                details: validation.errors,
              },
            });
          }
        }
        
        // Add validation metadata
        const validatedResponse = {
          ...data,
          _validation: {
            validated: true,
            schema: schema?.name,
            timestamp: new Date().toISOString(),
          },
        };
        
        return JSON.stringify(validatedResponse);
      }
      
      return responseBuffer;
    }),
  },
});

Performance and Caching

Response processing focused on performance optimization, caching, and content compression.

Performance Optimization Examples:

// Response caching and optimization
const cachingProxy = createProxyMiddleware({
  target: "http://api.example.com",
  selfHandleResponse: true,
  on: {
    proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
      const cacheKey = generateCacheKey(req);
      const contentType = proxyRes.headers?.["content-type"];
      const statusCode = proxyRes.statusCode || 200;
      
      // Cache successful responses
      if (statusCode === 200 && shouldCache(req, proxyRes)) {
        await setCache(cacheKey, responseBuffer, {
          ttl: getCacheTTL(req),
          contentType,
          headers: proxyRes.headers,
        });
        
        // Add cache headers
        res.setHeader("X-Cache", "MISS");
        res.setHeader("X-Cache-Key", cacheKey);
        res.setHeader("Cache-Control", `public, max-age=${getCacheTTL(req)}`);
      }
      
      // Optimize JSON responses
      if (contentType?.includes("application/json") && statusCode === 200) {
        const data = JSON.parse(responseBuffer.toString("utf8"));
        
        // Remove null values and optimize structure
        const optimizedData = optimizeJsonResponse(data);
        
        // Compress if client supports it
        const acceptEncoding = req.headers["accept-encoding"];
        if (acceptEncoding?.includes("gzip")) {
          const compressed = await gzipCompress(JSON.stringify(optimizedData));
          res.setHeader("Content-Encoding", "gzip");
          return compressed;
        }
        
        return JSON.stringify(optimizedData);
      }
      
      return responseBuffer;
    }),
  },
});

// Streaming response processing for large responses
const streamingProxy = createProxyMiddleware({
  target: "http://large-data-api.example.com",
  selfHandleResponse: true,
  on: {
    proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
      const contentLength = parseInt(proxyRes.headers?.["content-length"] || "0");
      
      // For large responses, process in chunks
      if (contentLength > 1024 * 1024) { // > 1MB
        const contentType = proxyRes.headers?.["content-type"];
        
        if (contentType?.includes("application/json")) {
          // Stream JSON processing for large datasets
          const data = JSON.parse(responseBuffer.toString("utf8"));
          
          if (Array.isArray(data)) {
            // Process array items in chunks
            const processedItems = await processLargeArray(data, {
              chunkSize: 1000,
              processor: (chunk) => chunk.map(item => transformLargeItem(item)),
            });
            
            return JSON.stringify(processedItems);
          }
        }
      }
      
      return responseBuffer;
    }),
  },
});