@push.rocks/smartproxy
Version:
A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.
211 lines • 18.9 kB
JavaScript
import * as plugins from '../../../plugins.js';
import { createLogger } from '../models/types.js';
import { HttpStatus, getStatusText } from '../models/http-types.js';
/**
* Handles static routes including ACME challenges
*/
export class StaticHandler {
/**
* Handle static routes
*/
static async handleStatic(socket, route, context, record, initialChunk) {
const { connectionId, connectionManager, settings } = context;
const logger = context.logger || createLogger(settings.logLevel || 'info');
if (!route.action.handler) {
logger.error(`[${connectionId}] Static route '${route.name}' has no handler`);
socket.end();
connectionManager.cleanupConnection(record, 'no_handler');
return;
}
let buffer = Buffer.alloc(0);
let processingData = false;
const handleHttpData = async (chunk) => {
// Accumulate the data
buffer = Buffer.concat([buffer, chunk]);
// Prevent concurrent processing of the same buffer
if (processingData)
return;
processingData = true;
try {
// Process data until we have a complete request or need more data
await processBuffer();
}
finally {
processingData = false;
}
};
const processBuffer = async () => {
// Look for end of HTTP headers
const headerEndIndex = buffer.indexOf('\r\n\r\n');
if (headerEndIndex === -1) {
// Need more data
if (buffer.length > 8192) {
// Prevent excessive buffering
logger.error(`[${connectionId}] HTTP headers too large`);
socket.end();
connectionManager.cleanupConnection(record, 'headers_too_large');
}
return; // Wait for more data to arrive
}
// Parse the HTTP request
const headerBuffer = buffer.slice(0, headerEndIndex);
const headers = headerBuffer.toString();
const lines = headers.split('\r\n');
if (lines.length === 0) {
logger.error(`[${connectionId}] Invalid HTTP request`);
socket.end();
connectionManager.cleanupConnection(record, 'invalid_request');
return;
}
// Parse request line
const requestLine = lines[0];
const requestParts = requestLine.split(' ');
if (requestParts.length < 3) {
logger.error(`[${connectionId}] Invalid HTTP request line`);
socket.end();
connectionManager.cleanupConnection(record, 'invalid_request_line');
return;
}
const [method, path, httpVersion] = requestParts;
// Parse headers
const headersMap = {};
for (let i = 1; i < lines.length; i++) {
const colonIndex = lines[i].indexOf(':');
if (colonIndex > 0) {
const key = lines[i].slice(0, colonIndex).trim().toLowerCase();
const value = lines[i].slice(colonIndex + 1).trim();
headersMap[key] = value;
}
}
// Check for Content-Length to handle request body
const requestBodyLength = parseInt(headersMap['content-length'] || '0', 10);
const bodyStartIndex = headerEndIndex + 4; // Skip the \r\n\r\n
// If there's a body, ensure we have the full body
if (requestBodyLength > 0) {
const totalExpectedLength = bodyStartIndex + requestBodyLength;
// If we don't have the complete body yet, wait for more data
if (buffer.length < totalExpectedLength) {
// Implement a reasonable body size limit to prevent memory issues
if (requestBodyLength > 1024 * 1024) {
// 1MB limit
logger.error(`[${connectionId}] Request body too large`);
socket.end();
connectionManager.cleanupConnection(record, 'body_too_large');
return;
}
return; // Wait for more data
}
}
// Extract query string if present
let pathname = path;
let query;
const queryIndex = path.indexOf('?');
if (queryIndex !== -1) {
pathname = path.slice(0, queryIndex);
query = path.slice(queryIndex + 1);
}
try {
// Get request body if present
let requestBody;
if (requestBodyLength > 0) {
requestBody = buffer.slice(bodyStartIndex, bodyStartIndex + requestBodyLength);
}
// Pause socket to prevent data loss during async processing
socket.pause();
// Remove the data listener since we're handling the request
socket.removeListener('data', handleHttpData);
// Build route context with parsed HTTP information
const context = {
port: record.localPort,
domain: record.lockedDomain || headersMap['host']?.split(':')[0],
clientIp: record.remoteIP,
serverIp: socket.localAddress,
path: pathname,
query: query,
headers: headersMap,
isTls: record.isTLS,
tlsVersion: record.tlsVersion,
routeName: route.name,
routeId: route.id,
timestamp: Date.now(),
connectionId,
};
// Since IRouteContext doesn't have a body property,
// we need an alternative approach to handle the body
let response;
if (requestBody) {
if (settings.enableDetailedLogging) {
logger.info(`[${connectionId}] Processing request with body (${requestBody.length} bytes)`);
}
// Pass the body as an additional parameter by extending the context object
// This is not type-safe, but it allows handlers that expect a body to work
const extendedContext = {
...context,
// Provide both raw buffer and string representation
requestBody: requestBody,
requestBodyText: requestBody.toString(),
method: method,
};
// Call the handler with the extended context
// The handler needs to know to look for the non-standard properties
response = await route.action.handler(extendedContext);
}
else {
// Call the handler with the standard context
const extendedContext = {
...context,
method: method,
};
response = await route.action.handler(extendedContext);
}
// Prepare the HTTP response
const responseHeaders = response.headers || {};
const contentLength = Buffer.byteLength(response.body || '');
responseHeaders['Content-Length'] = contentLength.toString();
if (!responseHeaders['Content-Type']) {
responseHeaders['Content-Type'] = 'text/plain';
}
// Build the response
let httpResponse = `HTTP/1.1 ${response.status} ${getStatusText(response.status)}\r\n`;
for (const [key, value] of Object.entries(responseHeaders)) {
httpResponse += `${key}: ${value}\r\n`;
}
httpResponse += '\r\n';
// Send response
socket.write(httpResponse);
if (response.body) {
socket.write(response.body);
}
socket.end();
connectionManager.cleanupConnection(record, 'completed');
}
catch (error) {
logger.error(`[${connectionId}] Error in static handler: ${error}`);
// Send error response
const errorResponse = 'HTTP/1.1 500 Internal Server Error\r\n' +
'Content-Type: text/plain\r\n' +
'Content-Length: 21\r\n' +
'\r\n' +
'Internal Server Error';
socket.write(errorResponse);
socket.end();
connectionManager.cleanupConnection(record, 'handler_error');
}
};
// Process initial chunk if provided
if (initialChunk && initialChunk.length > 0) {
if (settings.enableDetailedLogging) {
logger.info(`[${connectionId}] Processing initial data chunk (${initialChunk.length} bytes)`);
}
// Process the initial chunk immediately
handleHttpData(initialChunk);
}
// Listen for additional data
socket.on('data', handleHttpData);
// Ensure cleanup on socket close
socket.once('close', () => {
socket.removeListener('data', handleHttpData);
});
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RhdGljLWhhbmRsZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90cy9wcm94aWVzL2h0dHAtcHJveHkvaGFuZGxlcnMvc3RhdGljLWhhbmRsZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxxQkFBcUIsQ0FBQztBQUkvQyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFbEQsT0FBTyxFQUFFLFVBQVUsRUFBRSxhQUFhLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQVNwRTs7R0FFRztBQUNILE1BQU0sT0FBTyxhQUFhO0lBQ3hCOztPQUVHO0lBQ0ksTUFBTSxDQUFDLEtBQUssQ0FBQyxZQUFZLENBQzlCLE1BQTBCLEVBQzFCLEtBQW1CLEVBQ25CLE9BQThCLEVBQzlCLE1BQXlCLEVBQ3pCLFlBQXFCO1FBRXJCLE1BQU0sRUFBRSxZQUFZLEVBQUUsaUJBQWlCLEVBQUUsUUFBUSxFQUFFLEdBQUcsT0FBTyxDQUFDO1FBQzlELE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxNQUFNLElBQUksWUFBWSxDQUFDLFFBQVEsQ0FBQyxRQUFRLElBQUksTUFBTSxDQUFDLENBQUM7UUFFM0UsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDMUIsTUFBTSxDQUFDLEtBQUssQ0FBQyxJQUFJLFlBQVksbUJBQW1CLEtBQUssQ0FBQyxJQUFJLGtCQUFrQixDQUFDLENBQUM7WUFDOUUsTUFBTSxDQUFDLEdBQUcsRUFBRSxDQUFDO1lBQ2IsaUJBQWlCLENBQUMsaUJBQWlCLENBQUMsTUFBTSxFQUFFLFlBQVksQ0FBQyxDQUFDO1lBQzFELE9BQU87UUFDVCxDQUFDO1FBRUQsSUFBSSxNQUFNLEdBQUcsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUM3QixJQUFJLGNBQWMsR0FBRyxLQUFLLENBQUM7UUFFM0IsTUFBTSxj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