or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

body-processing.mderror-handling.mdfiltering.mdindex.mdnetwork-configuration.mdpath-resolution.mdrequest-modification.mdresponse-processing.md
tile.json

response-processing.mddocs/

Response Processing

Modify proxy responses before sending them to the client. This includes transforming response data, modifying headers, and handling various content types including JSON, text, and binary data.

Capabilities

User Response Decorator

Modify the proxy response data before sending it to the client. This function receives the complete response and allows full transformation.

/**
 * Modify response data before sending to client
 * @param {object} proxyRes - The proxy response object
 * @param {any} proxyResData - The response data from the proxy
 * @param {object} userReq - The original user request
 * @param {object} userRes - The user response object
 * @returns {any|Promise<any>} Modified response data
 */
userResDecorator: (proxyRes, proxyResData, userReq, userRes) => any | Promise<any>;

Usage Examples:

const proxy = require('express-http-proxy');

// JSON response transformation
app.use('/api', proxy('backend.example.com', {
  userResDecorator: function(proxyRes, proxyResData, userReq, userRes) {
    // Parse JSON response
    const data = JSON.parse(proxyResData.toString('utf8'));
    
    // Add metadata
    data.metadata = {
      processedAt: new Date().toISOString(),
      proxy: true,
      version: '2.1.1'
    };
    
    // Transform field names
    if (data.user_id) {
      data.userId = data.user_id;
      delete data.user_id;
    }
    
    return JSON.stringify(data);
  }
}));

// Filter sensitive data
app.use('/users', proxy('user-service.com', {
  userResDecorator: function(proxyRes, proxyResData, userReq, userRes) {
    const users = JSON.parse(proxyResData.toString('utf8'));
    
    // Remove sensitive fields
    const sanitizedUsers = users.map(user => ({
      id: user.id,
      name: user.name,
      email: user.email
      // Remove: password, ssn, internal_notes, etc.
    }));
    
    return JSON.stringify(sanitizedUsers);
  }
}));

Promise-Based Response Processing

Use Promises for asynchronous response transformation operations.

/**
 * Promise-based response decorator
 * @param {object} proxyRes - The proxy response object
 * @param {any} proxyResData - The response data from the proxy
 * @param {object} userReq - The original user request
 * @param {object} userRes - The user response object
 * @returns {Promise<any>} Promise resolving to modified response data
 */
userResDecorator: (proxyRes, proxyResData, userReq, userRes) => Promise<any>;

Usage Examples:

// Async data enrichment
app.use('/products', proxy('product-service.com', {
  userResDecorator: async function(proxyRes, proxyResData, userReq, userRes) {
    const products = JSON.parse(proxyResData.toString('utf8'));
    
    // Enrich each product with additional data
    const enrichedProducts = await Promise.all(
      products.map(async (product) => ({
        ...product,
        reviews: await reviewService.getReviews(product.id),
        inventory: await inventoryService.getStock(product.id)
      }))
    );
    
    return JSON.stringify(enrichedProducts);
  }
}));

// External service integration
app.use('/translate', proxy('content-service.com', {
  userResDecorator: function(proxyRes, proxyResData, userReq, userRes) {
    return new Promise((resolve, reject) => {
      const content = JSON.parse(proxyResData.toString('utf8'));
      
      // Translate content if needed
      if (userReq.headers['accept-language']?.startsWith('es')) {
        translationService.translate(content, 'es')
          .then(translatedContent => {
            resolve(JSON.stringify(translatedContent));
          })
          .catch(reject);
      } else {
        resolve(JSON.stringify(content));
      }
    });
  }
}));

Response Header Decorator

Modify response headers before sending to the client. This decorator replaces (rather than merges) the headers.

/**
 * Modify response headers before sending to client
 * @param {object} headers - The response headers object
 * @param {object} userReq - The original user request
 * @param {object} userRes - The user response object
 * @param {object} proxyReq - The proxy request object
 * @param {object} proxyRes - The proxy response object
 * @returns {object} Modified headers object
 */
userResHeaderDecorator: (headers, userReq, userRes, proxyReq, proxyRes) => object;

Usage Examples:

// Security headers
app.use('/secure', proxy('secure-api.com', {
  userResHeaderDecorator: function(headers, userReq, userRes, proxyReq, proxyRes) {
    // Add security headers
    headers['X-Frame-Options'] = 'DENY';
    headers['X-Content-Type-Options'] = 'nosniff';
    headers['X-XSS-Protection'] = '1; mode=block';
    
    // Remove internal headers
    delete headers['x-internal-server'];
    delete headers['x-debug-info'];
    
    // Add CORS headers if needed
    if (userReq.headers.origin) {
      headers['Access-Control-Allow-Origin'] = userReq.headers.origin;
      headers['Access-Control-Allow-Credentials'] = 'true';
    }
    
    return headers;
  }
}));

// Caching headers
app.use('/static', proxy('cdn.example.com', {
  userResHeaderDecorator: function(headers, userReq, userRes, proxyReq, proxyRes) {
    // Set appropriate caching headers
    if (proxyRes.statusCode === 200) {
      headers['Cache-Control'] = 'public, max-age=3600';
      headers['ETag'] = `"${Date.now()}"`;
    }
    
    // Add custom headers
    headers['X-Served-By'] = 'express-http-proxy';
    headers['X-Response-Time'] = Date.now() - userReq.startTime;
    
    return headers;
  }
}));

Response Processing Behavior

Gzip Handling

If the proxy response is gzipped, express-http-proxy automatically:

  1. Unzips the response before passing to userResDecorator
  2. Re-zips the response after processing
  3. Sends the compressed response to the client

This behavior cannot currently be disabled.

304 Not Modified

When the proxied service returns 304 Not Modified, the userResDecorator step is skipped since there is no body to process.

Streaming vs Buffering

  • Streaming mode (default): Responses are piped directly for optimal performance
  • Buffering mode: Automatically enabled when using response decorators
    • Required for response modification
    • May impact performance with large payloads
    • Necessary for userResDecorator and userResHeaderDecorator

Reference Exploitation Warning

While the proxyRes, userReq, and userRes arguments are passed by reference and can be modified, this is not a reliable interface and may be removed in future versions. Use the appropriate decorators for modifications.

Error Handling in Response Processing

// Handle errors in response processing
app.use('/error-prone', proxy('unreliable-service.com', {
  userResDecorator: function(proxyRes, proxyResData, userReq, userRes) {
    try {
      const data = JSON.parse(proxyResData.toString('utf8'));
      return JSON.stringify(data);
    } catch (error) {
      // Return error response or original data
      console.error('Response processing error:', error);
      return JSON.stringify({ error: 'Response processing failed' });
    }
  }
}));

Deprecated Options

intercept (Deprecated)

The intercept option has been deprecated. Use userResDecorator instead.

/**
 * @deprecated Use userResDecorator instead
 */
intercept?: (proxyRes, proxyResData, userReq, userRes, callback) => void;

Migration Example:

// Old intercept usage
app.use('/old', proxy('example.com', {
  intercept: function(proxyRes, proxyResData, userReq, userRes, cb) {
    const data = JSON.parse(proxyResData.toString('utf8'));
    data.newProperty = 'exciting data';
    cb(null, JSON.stringify(data));
  }
}));

// New userResDecorator usage
app.use('/new', proxy('example.com', {
  userResDecorator: function(proxyRes, proxyResData, userReq, userRes) {
    const data = JSON.parse(proxyResData.toString('utf8'));
    data.newProperty = 'exciting data';
    return JSON.stringify(data);
  }
}));