Response interception and manipulation capabilities for modifying upstream responses before they reach the client.
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:
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;
}),
},
});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);
}),
},
});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;
}),
},
});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;
}),
},
});