Extensible plugin architecture for customizing proxy server behavior and adding additional functionality through the underlying http-proxy server instance.
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:
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],
});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,
],
});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:
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:
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:
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);
};