149 lines
4.3 KiB
TypeScript
149 lines
4.3 KiB
TypeScript
import * as plugins from '../../plugins.js';
|
|
import { ForwardingHandler } from './base-handler.js';
|
|
import type { IForwardConfig } from '../config/forwarding-types.js';
|
|
import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
|
|
|
|
/**
|
|
* Handler for HTTP-only forwarding
|
|
*/
|
|
export class HttpForwardingHandler extends ForwardingHandler {
|
|
/**
|
|
* Create a new HTTP forwarding handler
|
|
* @param config The forwarding configuration
|
|
*/
|
|
constructor(config: IForwardConfig) {
|
|
super(config);
|
|
|
|
// Validate that this is an HTTP-only configuration
|
|
if (config.type !== 'http-only') {
|
|
throw new Error(`Invalid configuration type for HttpForwardingHandler: ${config.type}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize the handler
|
|
* HTTP handler doesn't need special initialization
|
|
*/
|
|
public async initialize(): Promise<void> {
|
|
// Basic initialization from parent class
|
|
await super.initialize();
|
|
}
|
|
|
|
/**
|
|
* Handle a raw socket connection
|
|
* HTTP handler doesn't do much with raw sockets as it mainly processes
|
|
* parsed HTTP requests
|
|
*/
|
|
public handleConnection(socket: plugins.net.Socket): void {
|
|
// For HTTP, we mainly handle parsed requests, but we can still set up
|
|
// some basic connection tracking
|
|
const remoteAddress = socket.remoteAddress || 'unknown';
|
|
|
|
socket.on('close', (hadError) => {
|
|
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
|
remoteAddress,
|
|
hadError
|
|
});
|
|
});
|
|
|
|
socket.on('error', (error) => {
|
|
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
remoteAddress,
|
|
error: error.message
|
|
});
|
|
});
|
|
|
|
this.emit(ForwardingHandlerEvents.CONNECTED, {
|
|
remoteAddress
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle an HTTP request
|
|
* @param req The HTTP request
|
|
* @param res The HTTP response
|
|
*/
|
|
public handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void {
|
|
// Get the target from configuration
|
|
const target = this.getTargetFromConfig();
|
|
|
|
// Create a custom headers object with variables for substitution
|
|
const variables = {
|
|
clientIp: req.socket.remoteAddress || 'unknown'
|
|
};
|
|
|
|
// Prepare headers, merging with any custom headers from config
|
|
const headers = this.applyCustomHeaders(req.headers, variables);
|
|
|
|
// Create the proxy request options
|
|
const options = {
|
|
hostname: target.host,
|
|
port: target.port,
|
|
path: req.url,
|
|
method: req.method,
|
|
headers
|
|
};
|
|
|
|
// Create the proxy request
|
|
const proxyReq = plugins.http.request(options, (proxyRes) => {
|
|
// Copy status code and headers from the proxied response
|
|
res.writeHead(proxyRes.statusCode || 500, proxyRes.headers);
|
|
|
|
// Pipe the proxy response to the client response
|
|
proxyRes.pipe(res);
|
|
|
|
// Track bytes for logging
|
|
let responseSize = 0;
|
|
proxyRes.on('data', (chunk) => {
|
|
responseSize += chunk.length;
|
|
});
|
|
|
|
proxyRes.on('end', () => {
|
|
this.emit(ForwardingHandlerEvents.HTTP_RESPONSE, {
|
|
statusCode: proxyRes.statusCode,
|
|
headers: proxyRes.headers,
|
|
size: responseSize
|
|
});
|
|
});
|
|
});
|
|
|
|
// Handle errors in the proxy request
|
|
proxyReq.on('error', (error) => {
|
|
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
remoteAddress: req.socket.remoteAddress,
|
|
error: `Proxy request error: ${error.message}`
|
|
});
|
|
|
|
// Send an error response if headers haven't been sent yet
|
|
if (!res.headersSent) {
|
|
res.writeHead(502, { 'Content-Type': 'text/plain' });
|
|
res.end(`Error forwarding request: ${error.message}`);
|
|
} else {
|
|
// Just end the response if headers have already been sent
|
|
res.end();
|
|
}
|
|
});
|
|
|
|
// Track request details for logging
|
|
let requestSize = 0;
|
|
req.on('data', (chunk) => {
|
|
requestSize += chunk.length;
|
|
});
|
|
|
|
// Log the request
|
|
this.emit(ForwardingHandlerEvents.HTTP_REQUEST, {
|
|
method: req.method,
|
|
url: req.url,
|
|
headers: req.headers,
|
|
remoteAddress: req.socket.remoteAddress,
|
|
target: `${target.host}:${target.port}`
|
|
});
|
|
|
|
// Pipe the client request to the proxy request
|
|
if (req.readable) {
|
|
req.pipe(proxyReq);
|
|
} else {
|
|
proxyReq.end();
|
|
}
|
|
}
|
|
} |