fix(strcuture): refactor responsibilities

This commit is contained in:
2025-05-19 17:28:05 +00:00
parent 8fb67922a5
commit 465148d553
62 changed files with 1414 additions and 2066 deletions

View File

@ -2,7 +2,7 @@ import * as plugins from '../../plugins.js';
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { type INetworkProxyOptions, type ICertificateEntry, type ILogger, createLogger } from './models/types.js';
import { type IHttpProxyOptions, type ICertificateEntry, type ILogger, createLogger } from './models/types.js';
import type { IRouteConfig } from '../smart-proxy/models/route-types.js';
/**
@ -18,7 +18,7 @@ export class CertificateManager {
private logger: ILogger;
private httpsServer: plugins.https.Server | null = null;
constructor(private options: INetworkProxyOptions) {
constructor(private options: IHttpProxyOptions) {
this.certificateStoreDir = path.resolve(options.acme?.certificateStore || './certs');
this.logger = createLogger(options.logLevel || 'info');

View File

@ -1,5 +1,5 @@
import * as plugins from '../../plugins.js';
import { type INetworkProxyOptions, type IConnectionEntry, type ILogger, createLogger } from './models/types.js';
import { type IHttpProxyOptions, type IConnectionEntry, type ILogger, createLogger } from './models/types.js';
/**
* Manages a pool of backend connections for efficient reuse
@ -9,7 +9,7 @@ export class ConnectionPool {
private roundRobinPositions: Map<string, number> = new Map();
private logger: ILogger;
constructor(private options: INetworkProxyOptions) {
constructor(private options: IHttpProxyOptions) {
this.logger = createLogger(options.logLevel || 'info');
}

View File

@ -0,0 +1,6 @@
/**
* HTTP handlers for various route types
*/
export { RedirectHandler } from './redirect-handler.js';
export { StaticHandler } from './static-handler.js';

View File

@ -0,0 +1,105 @@
import * as plugins from '../../../plugins.js';
import type { IRouteConfig } from '../../smart-proxy/models/route-types.js';
import type { IConnectionRecord } from '../../smart-proxy/models/interfaces.js';
import type { ILogger } from '../models/types.js';
import { createLogger } from '../models/types.js';
import { HttpStatus, getStatusText } from '../models/http-types.js';
export interface IRedirectHandlerContext {
connectionId: string;
connectionManager: any; // Avoid circular deps
settings: any;
logger?: ILogger;
}
/**
* Handles HTTP redirect routes
*/
export class RedirectHandler {
/**
* Handle redirect routes
*/
public static async handleRedirect(
socket: plugins.net.Socket,
route: IRouteConfig,
context: IRedirectHandlerContext
): Promise<void> {
const { connectionId, connectionManager, settings } = context;
const logger = context.logger || createLogger(settings.logLevel || 'info');
const action = route.action;
// We should have a redirect configuration
if (!action.redirect) {
logger.error(`[${connectionId}] Redirect action missing redirect configuration`);
socket.end();
connectionManager.cleanupConnection({ id: connectionId }, 'missing_redirect');
return;
}
// For TLS connections, we can't do redirects at the TCP level
// This check should be done before calling this handler
// Wait for the first HTTP request to perform the redirect
const dataListeners: ((chunk: Buffer) => void)[] = [];
const httpDataHandler = (chunk: Buffer) => {
// Remove all data listeners to avoid duplicated processing
for (const listener of dataListeners) {
socket.removeListener('data', listener);
}
// Parse HTTP request to get path
try {
const headersEnd = chunk.indexOf('\r\n\r\n');
if (headersEnd === -1) {
// Not a complete HTTP request, need more data
socket.once('data', httpDataHandler);
dataListeners.push(httpDataHandler);
return;
}
const httpHeaders = chunk.slice(0, headersEnd).toString();
const requestLine = httpHeaders.split('\r\n')[0];
const [method, path] = requestLine.split(' ');
// Extract Host header
const hostMatch = httpHeaders.match(/Host: (.+?)(\r\n|\r|\n|$)/i);
const host = hostMatch ? hostMatch[1].trim() : '';
// Process the redirect URL with template variables
let redirectUrl = action.redirect.to;
redirectUrl = redirectUrl.replace(/\{domain\}/g, host);
redirectUrl = redirectUrl.replace(/\{path\}/g, path || '');
redirectUrl = redirectUrl.replace(/\{port\}/g, socket.localPort?.toString() || '80');
// Prepare the HTTP redirect response
const redirectResponse = [
`HTTP/1.1 ${action.redirect.status} Moved`,
`Location: ${redirectUrl}`,
'Connection: close',
'Content-Length: 0',
'',
'',
].join('\r\n');
if (settings.enableDetailedLogging) {
logger.info(
`[${connectionId}] Redirecting to ${redirectUrl} with status ${action.redirect.status}`
);
}
// Send the redirect response
socket.end(redirectResponse);
connectionManager.initiateCleanupOnce({ id: connectionId }, 'redirect_complete');
} catch (err) {
logger.error(`[${connectionId}] Error processing HTTP redirect: ${err}`);
socket.end();
connectionManager.initiateCleanupOnce({ id: connectionId }, 'redirect_error');
}
};
// Setup the HTTP data handler
socket.once('data', httpDataHandler);
dataListeners.push(httpDataHandler);
}
}

View File

@ -0,0 +1,251 @@
import * as plugins from '../../../plugins.js';
import type { IRouteConfig } from '../../smart-proxy/models/route-types.js';
import type { IConnectionRecord } from '../../smart-proxy/models/interfaces.js';
import type { ILogger } from '../models/types.js';
import { createLogger } from '../models/types.js';
import type { IRouteContext } from '../../../core/models/route-context.js';
import { HttpStatus, getStatusText } from '../models/http-types.js';
export interface IStaticHandlerContext {
connectionId: string;
connectionManager: any; // Avoid circular deps
settings: any;
logger?: ILogger;
}
/**
* Handles static routes including ACME challenges
*/
export class StaticHandler {
/**
* Handle static routes
*/
public static async handleStatic(
socket: plugins.net.Socket,
route: IRouteConfig,
context: IStaticHandlerContext,
record: IConnectionRecord
): Promise<void> {
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: Buffer) => {
// 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: Record<string, string> = {};
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: string | undefined;
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: Buffer | undefined;
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: IRouteContext = {
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 as any);
} else {
// Call the handler with the standard context
const extendedContext = {
...context,
method: method,
};
response = await route.action.handler(extendedContext as any);
}
// 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');
}
};
// Listen for data
socket.on('data', handleHttpData);
// Ensure cleanup on socket close
socket.once('close', () => {
socket.removeListener('data', handleHttpData);
});
}
}

View File

@ -5,7 +5,7 @@ import {
convertLegacyConfigToRouteConfig
} from './models/types.js';
import type {
INetworkProxyOptions,
IHttpProxyOptions,
ILogger,
IReverseProxyConfig
} from './models/types.js';
@ -16,21 +16,22 @@ import { CertificateManager } from './certificate-manager.js';
import { ConnectionPool } from './connection-pool.js';
import { RequestHandler, type IMetricsTracker } from './request-handler.js';
import { WebSocketHandler } from './websocket-handler.js';
import { ProxyRouter } from '../../http/router/index.js';
import { RouteRouter } from '../../http/router/route-router.js';
import { ProxyRouter } from '../../routing/router/index.js';
import { RouteRouter } from '../../routing/router/route-router.js';
import { FunctionCache } from './function-cache.js';
/**
* NetworkProxy provides a reverse proxy with TLS termination, WebSocket support,
* HttpProxy provides a reverse proxy with TLS termination, WebSocket support,
* automatic certificate management, and high-performance connection pooling.
* Handles all HTTP/HTTPS traffic including redirects, ACME challenges, and static routes.
*/
export class NetworkProxy implements IMetricsTracker {
export class HttpProxy implements IMetricsTracker {
// Provide a minimal JSON representation to avoid circular references during deep equality checks
public toJSON(): any {
return {};
}
// Configuration
public options: INetworkProxyOptions;
public options: IHttpProxyOptions;
public routes: IRouteConfig[] = [];
// Server instances (HTTP/2 with HTTP/1 fallback)
@ -66,9 +67,9 @@ export class NetworkProxy implements IMetricsTracker {
private logger: ILogger;
/**
* Creates a new NetworkProxy instance
* Creates a new HttpProxy instance
*/
constructor(optionsArg: INetworkProxyOptions) {
constructor(optionsArg: IHttpProxyOptions) {
// Set default options
this.options = {
port: optionsArg.port,
@ -155,7 +156,7 @@ export class NetworkProxy implements IMetricsTracker {
}
/**
* Returns the port number this NetworkProxy is listening on
* Returns the port number this HttpProxy is listening on
* Useful for SmartProxy to determine where to forward connections
*/
public getListeningPort(): number {
@ -202,7 +203,7 @@ export class NetworkProxy implements IMetricsTracker {
/**
* Returns current server metrics
* Useful for SmartProxy to determine which NetworkProxy to use for load balancing
* Useful for SmartProxy to determine which HttpProxy to use for load balancing
*/
public getMetrics(): any {
return {
@ -259,7 +260,7 @@ export class NetworkProxy implements IMetricsTracker {
// Start the server
return new Promise((resolve) => {
this.httpsServer.listen(this.options.port, () => {
this.logger.info(`NetworkProxy started on port ${this.options.port}`);
this.logger.info(`HttpProxy started on port ${this.options.port}`);
resolve();
});
});
@ -352,7 +353,7 @@ export class NetworkProxy implements IMetricsTracker {
}
/**
* Updates the route configurations - this is the primary method for configuring NetworkProxy
* Updates the route configurations - this is the primary method for configuring HttpProxy
* @param routes The new route configurations to use
*/
public async updateRouteConfigs(routes: IRouteConfig[]): Promise<void> {
@ -503,7 +504,7 @@ export class NetworkProxy implements IMetricsTracker {
* Stops the proxy server
*/
public async stop(): Promise<void> {
this.logger.info('Stopping NetworkProxy server');
this.logger.info('Stopping HttpProxy server');
// Clear intervals
if (this.metricsInterval) {
@ -534,7 +535,7 @@ export class NetworkProxy implements IMetricsTracker {
// Close the HTTPS server
return new Promise((resolve) => {
this.httpsServer.close(() => {
this.logger.info('NetworkProxy server stopped successfully');
this.logger.info('HttpProxy server stopped successfully');
resolve();
});
});

View File

@ -1,11 +1,11 @@
/**
* NetworkProxy implementation
* HttpProxy implementation
*/
// Re-export models
export * from './models/index.js';
// Export NetworkProxy and supporting classes
export { NetworkProxy } from './network-proxy.js';
// Export HttpProxy and supporting classes
export { HttpProxy } from './http-proxy.js';
export { CertificateManager } from './certificate-manager.js';
export { ConnectionPool } from './connection-pool.js';
export { RequestHandler } from './request-handler.js';

View File

@ -0,0 +1,165 @@
import * as plugins from '../../../plugins.js';
/**
* HTTP-specific event types
*/
export enum HttpEvents {
REQUEST_RECEIVED = 'request-received',
REQUEST_FORWARDED = 'request-forwarded',
REQUEST_HANDLED = 'request-handled',
REQUEST_ERROR = 'request-error',
}
/**
* HTTP status codes as an enum for better type safety
*/
export enum HttpStatus {
OK = 200,
MOVED_PERMANENTLY = 301,
FOUND = 302,
TEMPORARY_REDIRECT = 307,
PERMANENT_REDIRECT = 308,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
FORBIDDEN = 403,
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
REQUEST_TIMEOUT = 408,
TOO_MANY_REQUESTS = 429,
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503,
GATEWAY_TIMEOUT = 504,
}
/**
* Base error class for HTTP-related errors
*/
export class HttpError extends Error {
constructor(message: string, public readonly statusCode: HttpStatus = HttpStatus.INTERNAL_SERVER_ERROR) {
super(message);
this.name = 'HttpError';
}
}
/**
* Error related to certificate operations
*/
export class CertificateError extends HttpError {
constructor(
message: string,
public readonly domain: string,
public readonly isRenewal: boolean = false
) {
super(`${message} for domain ${domain}${isRenewal ? ' (renewal)' : ''}`, HttpStatus.INTERNAL_SERVER_ERROR);
this.name = 'CertificateError';
}
}
/**
* Error related to server operations
*/
export class ServerError extends HttpError {
constructor(message: string, public readonly code?: string, statusCode: HttpStatus = HttpStatus.INTERNAL_SERVER_ERROR) {
super(message, statusCode);
this.name = 'ServerError';
}
}
/**
* Error for bad requests
*/
export class BadRequestError extends HttpError {
constructor(message: string) {
super(message, HttpStatus.BAD_REQUEST);
this.name = 'BadRequestError';
}
}
/**
* Error for not found resources
*/
export class NotFoundError extends HttpError {
constructor(message: string = 'Resource not found') {
super(message, HttpStatus.NOT_FOUND);
this.name = 'NotFoundError';
}
}
/**
* Redirect configuration for HTTP requests
*/
export interface IRedirectConfig {
source: string; // Source path or pattern
destination: string; // Destination URL
type: HttpStatus; // Redirect status code
preserveQuery?: boolean; // Whether to preserve query parameters
}
/**
* HTTP router configuration
*/
export interface IRouterConfig {
routes: Array<{
path: string;
method?: string;
handler: (req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse) => void | Promise<void>;
}>;
notFoundHandler?: (req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse) => void;
errorHandler?: (error: Error, req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse) => void;
}
/**
* HTTP request method types
*/
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS' | 'CONNECT' | 'TRACE';
/**
* Helper function to get HTTP status text
*/
export function getStatusText(status: HttpStatus): string {
const statusTexts: Record<HttpStatus, string> = {
[HttpStatus.OK]: 'OK',
[HttpStatus.MOVED_PERMANENTLY]: 'Moved Permanently',
[HttpStatus.FOUND]: 'Found',
[HttpStatus.TEMPORARY_REDIRECT]: 'Temporary Redirect',
[HttpStatus.PERMANENT_REDIRECT]: 'Permanent Redirect',
[HttpStatus.BAD_REQUEST]: 'Bad Request',
[HttpStatus.UNAUTHORIZED]: 'Unauthorized',
[HttpStatus.FORBIDDEN]: 'Forbidden',
[HttpStatus.NOT_FOUND]: 'Not Found',
[HttpStatus.METHOD_NOT_ALLOWED]: 'Method Not Allowed',
[HttpStatus.REQUEST_TIMEOUT]: 'Request Timeout',
[HttpStatus.TOO_MANY_REQUESTS]: 'Too Many Requests',
[HttpStatus.INTERNAL_SERVER_ERROR]: 'Internal Server Error',
[HttpStatus.NOT_IMPLEMENTED]: 'Not Implemented',
[HttpStatus.BAD_GATEWAY]: 'Bad Gateway',
[HttpStatus.SERVICE_UNAVAILABLE]: 'Service Unavailable',
[HttpStatus.GATEWAY_TIMEOUT]: 'Gateway Timeout',
};
return statusTexts[status] || 'Unknown';
}
// Legacy interfaces for backward compatibility
export interface IDomainOptions {
domainName: string;
sslRedirect: boolean;
acmeMaintenance: boolean;
forward?: { ip: string; port: number };
acmeForward?: { ip: string; port: number };
}
export interface IDomainCertificate {
options: IDomainOptions;
certObtained: boolean;
obtainingInProgress: boolean;
certificate?: string;
privateKey?: string;
expiryDate?: Date;
lastRenewalAttempt?: Date;
}
// Backward compatibility exports
export { HttpError as Port80HandlerError };
export { CertificateError as CertError };

View File

@ -0,0 +1,5 @@
/**
* HttpProxy models
*/
export * from './types.js';
export * from './http-types.js';

View File

@ -16,9 +16,9 @@ import type { IRouteConfig } from '../../smart-proxy/models/route-types.js';
import type { IRouteContext } from '../../../core/models/route-context.js';
/**
* Configuration options for NetworkProxy
* Configuration options for HttpProxy
*/
export interface INetworkProxyOptions {
export interface IHttpProxyOptions {
port: number;
maxConnections?: number;
keepAliveTimeout?: number;

View File

@ -1,14 +1,14 @@
import * as plugins from '../../plugins.js';
import '../../core/models/socket-augmentation.js';
import {
type INetworkProxyOptions,
type IHttpProxyOptions,
type ILogger,
createLogger,
type IReverseProxyConfig,
RouteManager
} from './models/types.js';
import { ConnectionPool } from './connection-pool.js';
import { ProxyRouter } from '../../http/router/index.js';
import { ProxyRouter } from '../../routing/router/index.js';
import { ContextCreator } from './context-creator.js';
import { HttpRequestHandler } from './http-request-handler.js';
import { Http2RequestHandler } from './http2-request-handler.js';
@ -46,7 +46,7 @@ export class RequestHandler {
public securityManager: SecurityManager;
constructor(
private options: INetworkProxyOptions,
private options: IHttpProxyOptions,
private connectionPool: ConnectionPool,
private legacyRouter: ProxyRouter, // Legacy router for backward compatibility
private routeManager?: RouteManager,

View File

@ -1,8 +1,8 @@
import * as plugins from '../../plugins.js';
import '../../core/models/socket-augmentation.js';
import { type INetworkProxyOptions, type IWebSocketWithHeartbeat, type ILogger, createLogger, type IReverseProxyConfig } from './models/types.js';
import { type IHttpProxyOptions, type IWebSocketWithHeartbeat, type ILogger, createLogger, type IReverseProxyConfig } from './models/types.js';
import { ConnectionPool } from './connection-pool.js';
import { ProxyRouter, RouteRouter } from '../../http/router/index.js';
import { ProxyRouter, RouteRouter } from '../../routing/router/index.js';
import type { IRouteConfig } from '../smart-proxy/models/route-types.js';
import type { IRouteContext } from '../../core/models/route-context.js';
import { toBaseContext } from '../../core/models/route-context.js';
@ -23,7 +23,7 @@ export class WebSocketHandler {
private securityManager: SecurityManager;
constructor(
private options: INetworkProxyOptions,
private options: IHttpProxyOptions,
private connectionPool: ConnectionPool,
private legacyRouter: ProxyRouter, // Legacy router for backward compatibility
private routes: IRouteConfig[] = [] // Routes for modern router

View File

@ -2,15 +2,15 @@
* Proxy implementations module
*/
// Export NetworkProxy with selective imports to avoid conflicts
export { NetworkProxy, CertificateManager, ConnectionPool, RequestHandler, WebSocketHandler } from './network-proxy/index.js';
export type { IMetricsTracker, MetricsTracker } from './network-proxy/index.js';
// Export network-proxy models except IAcmeOptions
export type { INetworkProxyOptions, ICertificateEntry, ILogger } from './network-proxy/models/types.js';
export { RouteManager as NetworkProxyRouteManager } from './network-proxy/models/types.js';
// Export HttpProxy with selective imports to avoid conflicts
export { HttpProxy, CertificateManager, ConnectionPool, RequestHandler, WebSocketHandler } from './http-proxy/index.js';
export type { IMetricsTracker, MetricsTracker } from './http-proxy/index.js';
// Export http-proxy models except IAcmeOptions
export type { IHttpProxyOptions, ICertificateEntry, ILogger } from './http-proxy/models/types.js';
export { RouteManager as HttpProxyRouteManager } from './http-proxy/models/types.js';
// Export SmartProxy with selective imports to avoid conflicts
export { SmartProxy, ConnectionManager, SecurityManager, TimeoutManager, TlsManager, NetworkProxyBridge, RouteConnectionHandler } from './smart-proxy/index.js';
export { SmartProxy, ConnectionManager, SecurityManager, TimeoutManager, TlsManager, HttpProxyBridge, RouteConnectionHandler } from './smart-proxy/index.js';
export { RouteManager as SmartProxyRouteManager } from './smart-proxy/route-manager.js';
export * from './smart-proxy/utils/index.js';
// Export smart-proxy models except IAcmeOptions

View File

@ -1,4 +0,0 @@
/**
* NetworkProxy models
*/
export * from './types.js';

View File

@ -1,5 +1,5 @@
import * as plugins from '../../plugins.js';
import { NetworkProxy } from '../network-proxy/index.js';
import { HttpProxy } from '../http-proxy/index.js';
import type { IRouteConfig, IRouteTls } from './models/route-types.js';
import type { IAcmeOptions } from './models/interfaces.js';
import { CertStore } from './cert-store.js';
@ -25,7 +25,7 @@ export interface ICertificateData {
export class SmartCertManager {
private certStore: CertStore;
private smartAcme: plugins.smartacme.SmartAcme | null = null;
private networkProxy: NetworkProxy | null = null;
private httpProxy: HttpProxy | null = null;
private renewalTimer: NodeJS.Timeout | null = null;
private pendingChallenges: Map<string, string> = new Map();
private challengeRoute: IRouteConfig | null = null;
@ -68,8 +68,8 @@ export class SmartCertManager {
}
}
public setNetworkProxy(networkProxy: NetworkProxy): void {
this.networkProxy = networkProxy;
public setHttpProxy(httpProxy: HttpProxy): void {
this.httpProxy = httpProxy;
}
@ -336,23 +336,23 @@ export class SmartCertManager {
}
/**
* Apply certificate to NetworkProxy
* Apply certificate to HttpProxy
*/
private async applyCertificate(domain: string, certData: ICertificateData): Promise<void> {
if (!this.networkProxy) {
console.warn('NetworkProxy not set, cannot apply certificate');
if (!this.httpProxy) {
console.warn('HttpProxy not set, cannot apply certificate');
return;
}
// Apply certificate to NetworkProxy
this.networkProxy.updateCertificate(domain, certData.cert, certData.key);
// Apply certificate to HttpProxy
this.httpProxy.updateCertificate(domain, certData.cert, certData.key);
// Also apply for wildcard if it's a subdomain
if (domain.includes('.') && !domain.startsWith('*.')) {
const parts = domain.split('.');
if (parts.length >= 2) {
const wildcardDomain = `*.${parts.slice(-2).join('.')}`;
this.networkProxy.updateCertificate(wildcardDomain, certData.cert, certData.key);
this.httpProxy.updateCertificate(wildcardDomain, certData.cert, certData.key);
}
}
}

View File

@ -1,47 +1,47 @@
import * as plugins from '../../plugins.js';
import { NetworkProxy } from '../network-proxy/index.js';
import { HttpProxy } from '../http-proxy/index.js';
import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js';
import type { IRouteConfig } from './models/route-types.js';
export class NetworkProxyBridge {
private networkProxy: NetworkProxy | null = null;
export class HttpProxyBridge {
private httpProxy: HttpProxy | null = null;
constructor(private settings: ISmartProxyOptions) {}
/**
* Get the NetworkProxy instance
* Get the HttpProxy instance
*/
public getNetworkProxy(): NetworkProxy | null {
return this.networkProxy;
public getHttpProxy(): HttpProxy | null {
return this.httpProxy;
}
/**
* Initialize NetworkProxy instance
* Initialize HttpProxy instance
*/
public async initialize(): Promise<void> {
if (!this.networkProxy && this.settings.useNetworkProxy && this.settings.useNetworkProxy.length > 0) {
const networkProxyOptions: any = {
port: this.settings.networkProxyPort!,
if (!this.httpProxy && this.settings.useHttpProxy && this.settings.useHttpProxy.length > 0) {
const httpProxyOptions: any = {
port: this.settings.httpProxyPort!,
portProxyIntegration: true,
logLevel: this.settings.enableDetailedLogging ? 'debug' : 'info'
};
this.networkProxy = new NetworkProxy(networkProxyOptions);
console.log(`Initialized NetworkProxy on port ${this.settings.networkProxyPort}`);
this.httpProxy = new HttpProxy(httpProxyOptions);
console.log(`Initialized HttpProxy on port ${this.settings.httpProxyPort}`);
// Apply route configurations to NetworkProxy
await this.syncRoutesToNetworkProxy(this.settings.routes || []);
// Apply route configurations to HttpProxy
await this.syncRoutesToHttpProxy(this.settings.routes || []);
}
}
/**
* Sync routes to NetworkProxy
* Sync routes to HttpProxy
*/
public async syncRoutesToNetworkProxy(routes: IRouteConfig[]): Promise<void> {
if (!this.networkProxy) return;
public async syncRoutesToHttpProxy(routes: IRouteConfig[]): Promise<void> {
if (!this.httpProxy) return;
// Convert routes to NetworkProxy format
const networkProxyConfigs = routes
// Convert routes to HttpProxy format
const httpProxyConfigs = routes
.filter(route => {
// Check if this route matches any of the specified network proxy ports
const routePorts = Array.isArray(route.match.ports)
@ -49,20 +49,20 @@ export class NetworkProxyBridge {
: [route.match.ports];
return routePorts.some(port =>
this.settings.useNetworkProxy?.includes(port)
this.settings.useHttpProxy?.includes(port)
);
})
.map(route => this.routeToNetworkProxyConfig(route));
.map(route => this.routeToHttpProxyConfig(route));
// Apply configurations to NetworkProxy
await this.networkProxy.updateRouteConfigs(networkProxyConfigs);
// Apply configurations to HttpProxy
await this.httpProxy.updateRouteConfigs(httpProxyConfigs);
}
/**
* Convert route to NetworkProxy configuration
* Convert route to HttpProxy configuration
*/
private routeToNetworkProxyConfig(route: IRouteConfig): any {
// Convert route to NetworkProxy domain config format
private routeToHttpProxyConfig(route: IRouteConfig): any {
// Convert route to HttpProxy domain config format
return {
domain: route.match.domains?.[0] || '*',
target: route.action.target,
@ -72,36 +72,36 @@ export class NetworkProxyBridge {
}
/**
* Check if connection should use NetworkProxy
* Check if connection should use HttpProxy
*/
public shouldUseNetworkProxy(connection: IConnectionRecord, routeMatch: any): boolean {
// Only use NetworkProxy for TLS termination
public shouldUseHttpProxy(connection: IConnectionRecord, routeMatch: any): boolean {
// Only use HttpProxy for TLS termination
return (
routeMatch.route.action.tls?.mode === 'terminate' ||
routeMatch.route.action.tls?.mode === 'terminate-and-reencrypt'
) && this.networkProxy !== null;
) && this.httpProxy !== null;
}
/**
* Forward connection to NetworkProxy
* Forward connection to HttpProxy
*/
public async forwardToNetworkProxy(
public async forwardToHttpProxy(
connectionId: string,
socket: plugins.net.Socket,
record: IConnectionRecord,
initialChunk: Buffer,
networkProxyPort: number,
httpProxyPort: number,
cleanupCallback: (reason: string) => void
): Promise<void> {
if (!this.networkProxy) {
throw new Error('NetworkProxy not initialized');
if (!this.httpProxy) {
throw new Error('HttpProxy not initialized');
}
const proxySocket = new plugins.net.Socket();
await new Promise<void>((resolve, reject) => {
proxySocket.connect(networkProxyPort, 'localhost', () => {
console.log(`[${connectionId}] Connected to NetworkProxy for termination`);
proxySocket.connect(httpProxyPort, 'localhost', () => {
console.log(`[${connectionId}] Connected to HttpProxy for termination`);
resolve();
});
@ -132,21 +132,21 @@ export class NetworkProxyBridge {
}
/**
* Start NetworkProxy
* Start HttpProxy
*/
public async start(): Promise<void> {
if (this.networkProxy) {
await this.networkProxy.start();
if (this.httpProxy) {
await this.httpProxy.start();
}
}
/**
* Stop NetworkProxy
* Stop HttpProxy
*/
public async stop(): Promise<void> {
if (this.networkProxy) {
await this.networkProxy.stop();
this.networkProxy = null;
if (this.httpProxy) {
await this.httpProxy.stop();
this.httpProxy = null;
}
}
}

View File

@ -14,12 +14,15 @@ export { ConnectionManager } from './connection-manager.js';
export { SecurityManager } from './security-manager.js';
export { TimeoutManager } from './timeout-manager.js';
export { TlsManager } from './tls-manager.js';
export { NetworkProxyBridge } from './network-proxy-bridge.js';
export { HttpProxyBridge } from './http-proxy-bridge.js';
// Export route-based components
export { RouteManager } from './route-manager.js';
export { RouteConnectionHandler } from './route-connection-handler.js';
export { NFTablesManager } from './nftables-manager.js';
// Export certificate management
export { SmartCertManager } from './certificate-manager.js';
// Export all helper functions from the utils directory
export * from './utils/index.js';

View File

@ -94,9 +94,9 @@ export interface ISmartProxyOptions {
keepAliveInactivityMultiplier?: number; // Multiplier for inactivity timeout for keep-alive connections
extendedKeepAliveLifetime?: number; // Extended lifetime for keep-alive connections (ms)
// NetworkProxy integration
useNetworkProxy?: number[]; // Array of ports to forward to NetworkProxy
networkProxyPort?: number; // Port where NetworkProxy is listening (default: 8443)
// HttpProxy integration
useHttpProxy?: number[]; // Array of ports to forward to HttpProxy
httpProxyPort?: number; // Port where HttpProxy is listening (default: 8443)
/**
* Global ACME configuration options for SmartProxy

View File

@ -60,10 +60,10 @@ export class PortManager {
// Start listening on the port
return new Promise<void>((resolve, reject) => {
server.listen(port, () => {
const isNetworkProxyPort = this.settings.useNetworkProxy?.includes(port);
const isHttpProxyPort = this.settings.useHttpProxy?.includes(port);
console.log(
`SmartProxy -> OK: Now listening on port ${port}${
isNetworkProxyPort ? ' (NetworkProxy forwarding enabled)' : ''
isHttpProxyPort ? ' (HttpProxy forwarding enabled)' : ''
}`
);

View File

@ -5,10 +5,11 @@ import type { IRouteConfig, IRouteAction, IRouteContext } from './models/route-t
import { ConnectionManager } from './connection-manager.js';
import { SecurityManager } from './security-manager.js';
import { TlsManager } from './tls-manager.js';
import { NetworkProxyBridge } from './network-proxy-bridge.js';
import { HttpProxyBridge } from './http-proxy-bridge.js';
import { TimeoutManager } from './timeout-manager.js';
import { RouteManager } from './route-manager.js';
import type { ForwardingHandler } from '../../forwarding/handlers/base-handler.js';
import { RedirectHandler, StaticHandler } from '../http-proxy/handlers/index.js';
/**
* Handles new connection processing and setup logic with support for route-based configuration
@ -24,7 +25,7 @@ export class RouteConnectionHandler {
private connectionManager: ConnectionManager,
private securityManager: SecurityManager,
private tlsManager: TlsManager,
private networkProxyBridge: NetworkProxyBridge,
private httpProxyBridge: HttpProxyBridge,
private timeoutManager: TimeoutManager,
private routeManager: RouteManager
) {
@ -530,22 +531,22 @@ export class RouteConnectionHandler {
case 'terminate':
case 'terminate-and-reencrypt':
// For TLS termination, use NetworkProxy
if (this.networkProxyBridge.getNetworkProxy()) {
// For TLS termination, use HttpProxy
if (this.httpProxyBridge.getHttpProxy()) {
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Using NetworkProxy for TLS termination to ${action.target.host}`
`[${connectionId}] Using HttpProxy for TLS termination to ${action.target.host}`
);
}
// If we have an initial chunk with TLS data, start processing it
if (initialChunk && record.isTLS) {
this.networkProxyBridge.forwardToNetworkProxy(
this.httpProxyBridge.forwardToHttpProxy(
connectionId,
socket,
record,
initialChunk,
this.settings.networkProxyPort,
this.settings.httpProxyPort || 8443,
(reason) => this.connectionManager.initiateCleanupOnce(record, reason)
);
return;
@ -557,9 +558,9 @@ export class RouteConnectionHandler {
this.connectionManager.cleanupConnection(record, 'tls_error');
return;
} else {
console.log(`[${connectionId}] NetworkProxy not available for TLS termination`);
console.log(`[${connectionId}] HttpProxy not available for TLS termination`);
socket.end();
this.connectionManager.cleanupConnection(record, 'no_network_proxy');
this.connectionManager.cleanupConnection(record, 'no_http_proxy');
return;
}
}
@ -621,87 +622,20 @@ export class RouteConnectionHandler {
record: IConnectionRecord,
route: IRouteConfig
): void {
const connectionId = record.id;
const action = route.action;
// We should have a redirect configuration
if (!action.redirect) {
console.log(`[${connectionId}] Redirect action missing redirect configuration`);
socket.end();
this.connectionManager.cleanupConnection(record, 'missing_redirect');
return;
}
// For TLS connections, we can't do redirects at the TCP level
if (record.isTLS) {
console.log(`[${connectionId}] Cannot redirect TLS connection at TCP level`);
console.log(`[${record.id}] Cannot redirect TLS connection at TCP level`);
socket.end();
this.connectionManager.cleanupConnection(record, 'tls_redirect_error');
return;
}
// Wait for the first HTTP request to perform the redirect
const dataListeners: ((chunk: Buffer) => void)[] = [];
const httpDataHandler = (chunk: Buffer) => {
// Remove all data listeners to avoid duplicated processing
for (const listener of dataListeners) {
socket.removeListener('data', listener);
}
// Parse HTTP request to get path
try {
const headersEnd = chunk.indexOf('\r\n\r\n');
if (headersEnd === -1) {
// Not a complete HTTP request, need more data
socket.once('data', httpDataHandler);
dataListeners.push(httpDataHandler);
return;
}
const httpHeaders = chunk.slice(0, headersEnd).toString();
const requestLine = httpHeaders.split('\r\n')[0];
const [method, path] = requestLine.split(' ');
// Extract Host header
const hostMatch = httpHeaders.match(/Host: (.+?)(\r\n|\r|\n|$)/i);
const host = hostMatch ? hostMatch[1].trim() : record.lockedDomain || '';
// Process the redirect URL with template variables
let redirectUrl = action.redirect.to;
redirectUrl = redirectUrl.replace(/\{domain\}/g, host);
redirectUrl = redirectUrl.replace(/\{path\}/g, path || '');
redirectUrl = redirectUrl.replace(/\{port\}/g, record.localPort.toString());
// Prepare the HTTP redirect response
const redirectResponse = [
`HTTP/1.1 ${action.redirect.status} Moved`,
`Location: ${redirectUrl}`,
'Connection: close',
'Content-Length: 0',
'',
'',
].join('\r\n');
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Redirecting to ${redirectUrl} with status ${action.redirect.status}`
);
}
// Send the redirect response
socket.end(redirectResponse);
this.connectionManager.initiateCleanupOnce(record, 'redirect_complete');
} catch (err) {
console.log(`[${connectionId}] Error processing HTTP redirect: ${err}`);
socket.end();
this.connectionManager.initiateCleanupOnce(record, 'redirect_error');
}
};
// Setup the HTTP data handler
socket.once('data', httpDataHandler);
dataListeners.push(httpDataHandler);
// Delegate to HttpProxy's RedirectHandler
RedirectHandler.handleRedirect(socket, route, {
connectionId: record.id,
connectionManager: this.connectionManager,
settings: this.settings
});
}
/**
@ -733,221 +667,12 @@ export class RouteConnectionHandler {
record: IConnectionRecord,
route: IRouteConfig
): Promise<void> {
const connectionId = record.id;
if (!route.action.handler) {
console.error(`[${connectionId}] Static route '${route.name}' has no handler`);
socket.end();
this.connectionManager.cleanupConnection(record, 'no_handler');
return;
}
let buffer = Buffer.alloc(0);
let processingData = false;
const handleHttpData = async (chunk: Buffer) => {
// 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
console.error(`[${connectionId}] HTTP headers too large`);
socket.end();
this.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) {
console.error(`[${connectionId}] Invalid HTTP request`);
socket.end();
this.connectionManager.cleanupConnection(record, 'invalid_request');
return;
}
// Parse request line
const requestLine = lines[0];
const requestParts = requestLine.split(' ');
if (requestParts.length < 3) {
console.error(`[${connectionId}] Invalid HTTP request line`);
socket.end();
this.connectionManager.cleanupConnection(record, 'invalid_request_line');
return;
}
const [method, path, httpVersion] = requestParts;
// Parse headers
const headersMap: Record<string, string> = {};
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
console.error(`[${connectionId}] Request body too large`);
socket.end();
this.connectionManager.cleanupConnection(record, 'body_too_large');
return;
}
return; // Wait for more data
}
}
// Extract query string if present
let pathname = path;
let query: string | undefined;
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: Buffer | undefined;
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: IRouteContext = {
port: record.localPort,
domain: record.lockedDomain || headersMap['host']?.split(':')[0],
clientIp: record.remoteIP,
serverIp: socket.localAddress!,
path: pathname,
query: query,
headers: headersMap,
method: method,
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 (this.settings.enableDetailedLogging) {
console.log(
`[${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(),
};
// 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 as any);
} else {
// Call the handler with the standard context
response = await route.action.handler(context);
}
// 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();
this.connectionManager.cleanupConnection(record, 'completed');
} catch (error) {
console.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();
this.connectionManager.cleanupConnection(record, 'handler_error');
}
};
// Listen for data
socket.on('data', handleHttpData);
// Ensure cleanup on socket close
socket.once('close', () => {
socket.removeListener('data', handleHttpData);
});
// Delegate to HttpProxy's StaticHandler
await StaticHandler.handleStatic(socket, route, {
connectionId: record.id,
connectionManager: this.connectionManager,
settings: this.settings
}, record);
}
/**
@ -1378,12 +1103,3 @@ export class RouteConnectionHandler {
}
}
// Helper function for status text
function getStatusText(status: number): string {
const statusTexts: Record<number, string> = {
200: 'OK',
404: 'Not Found',
500: 'Internal Server Error',
};
return statusTexts[status] || 'Unknown';
}

View File

@ -4,7 +4,7 @@ import * as plugins from '../../plugins.js';
import { ConnectionManager } from './connection-manager.js';
import { SecurityManager } from './security-manager.js';
import { TlsManager } from './tls-manager.js';
import { NetworkProxyBridge } from './network-proxy-bridge.js';
import { HttpProxyBridge } from './http-proxy-bridge.js';
import { TimeoutManager } from './timeout-manager.js';
import { PortManager } from './port-manager.js';
import { RouteManager } from './route-manager.js';
@ -49,7 +49,7 @@ export class SmartProxy extends plugins.EventEmitter {
private connectionManager: ConnectionManager;
private securityManager: SecurityManager;
private tlsManager: TlsManager;
private networkProxyBridge: NetworkProxyBridge;
private httpProxyBridge: HttpProxyBridge;
private timeoutManager: TimeoutManager;
public routeManager: RouteManager; // Made public for route management
private routeConnectionHandler: RouteConnectionHandler;
@ -123,7 +123,7 @@ export class SmartProxy extends plugins.EventEmitter {
keepAliveTreatment: settingsArg.keepAliveTreatment || 'extended',
keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6,
extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000,
networkProxyPort: settingsArg.networkProxyPort || 8443,
httpProxyPort: settingsArg.httpProxyPort || 8443,
};
// Normalize ACME options if provided (support both email and accountEmail)
@ -164,7 +164,7 @@ export class SmartProxy extends plugins.EventEmitter {
// Create other required components
this.tlsManager = new TlsManager(this.settings);
this.networkProxyBridge = new NetworkProxyBridge(this.settings);
this.httpProxyBridge = new HttpProxyBridge(this.settings);
// Initialize connection handler with route support
this.routeConnectionHandler = new RouteConnectionHandler(
@ -172,7 +172,7 @@ export class SmartProxy extends plugins.EventEmitter {
this.connectionManager,
this.securityManager,
this.tlsManager,
this.networkProxyBridge,
this.httpProxyBridge,
this.timeoutManager,
this.routeManager
);
@ -212,9 +212,9 @@ export class SmartProxy extends plugins.EventEmitter {
await this.updateRoutes(routes);
});
// Connect with NetworkProxy if available
if (this.networkProxyBridge.getNetworkProxy()) {
certManager.setNetworkProxy(this.networkProxyBridge.getNetworkProxy());
// Connect with HttpProxy if available
if (this.httpProxyBridge.getHttpProxy()) {
certManager.setHttpProxy(this.httpProxyBridge.getHttpProxy());
}
// Set the ACME state manager
@ -312,16 +312,16 @@ export class SmartProxy extends plugins.EventEmitter {
// Initialize certificate manager before starting servers
await this.initializeCertificateManager();
// Initialize and start NetworkProxy if needed
if (this.settings.useNetworkProxy && this.settings.useNetworkProxy.length > 0) {
await this.networkProxyBridge.initialize();
// Initialize and start HttpProxy if needed
if (this.settings.useHttpProxy && this.settings.useHttpProxy.length > 0) {
await this.httpProxyBridge.initialize();
// Connect NetworkProxy with certificate manager
// Connect HttpProxy with certificate manager
if (this.certManager) {
this.certManager.setNetworkProxy(this.networkProxyBridge.getNetworkProxy());
this.certManager.setHttpProxy(this.httpProxyBridge.getHttpProxy());
}
await this.networkProxyBridge.start();
await this.httpProxyBridge.start();
}
// Validate the route configuration
@ -368,7 +368,7 @@ export class SmartProxy extends plugins.EventEmitter {
let completedTlsHandshakes = 0;
let pendingTlsHandshakes = 0;
let keepAliveConnections = 0;
let networkProxyConnections = 0;
let httpProxyConnections = 0;
// Get connection records for analysis
const connectionRecords = this.connectionManager.getConnections();
@ -392,7 +392,7 @@ export class SmartProxy extends plugins.EventEmitter {
}
if (record.usingNetworkProxy) {
networkProxyConnections++;
httpProxyConnections++;
}
maxIncoming = Math.max(maxIncoming, now - record.incomingStartTime);
@ -408,7 +408,7 @@ export class SmartProxy extends plugins.EventEmitter {
console.log(
`Active connections: ${connectionRecords.size}. ` +
`Types: TLS=${tlsConnections} (Completed=${completedTlsHandshakes}, Pending=${pendingTlsHandshakes}), ` +
`Non-TLS=${nonTlsConnections}, KeepAlive=${keepAliveConnections}, NetworkProxy=${networkProxyConnections}. ` +
`Non-TLS=${nonTlsConnections}, KeepAlive=${keepAliveConnections}, HttpProxy=${httpProxyConnections}. ` +
`Longest running: IN=${plugins.prettyMs(maxIncoming)}, OUT=${plugins.prettyMs(maxOutgoing)}. ` +
`Termination stats: ${JSON.stringify({
IN: terminationStats.incoming,
@ -460,8 +460,8 @@ export class SmartProxy extends plugins.EventEmitter {
// Clean up all active connections
this.connectionManager.clearConnections();
// Stop NetworkProxy
await this.networkProxyBridge.stop();
// Stop HttpProxy
await this.httpProxyBridge.stop();
// Clear ACME state manager
this.acmeStateManager.clear();
@ -574,9 +574,9 @@ export class SmartProxy extends plugins.EventEmitter {
// Update settings with the new routes
this.settings.routes = newRoutes;
// If NetworkProxy is initialized, resync the configurations
if (this.networkProxyBridge.getNetworkProxy()) {
await this.networkProxyBridge.syncRoutesToNetworkProxy(newRoutes);
// If HttpProxy is initialized, resync the configurations
if (this.httpProxyBridge.getHttpProxy()) {
await this.httpProxyBridge.syncRoutesToHttpProxy(newRoutes);
}
// Update certificate manager with new routes
@ -711,14 +711,14 @@ export class SmartProxy extends plugins.EventEmitter {
let tlsConnections = 0;
let nonTlsConnections = 0;
let keepAliveConnections = 0;
let networkProxyConnections = 0;
let httpProxyConnections = 0;
// Analyze active connections
for (const record of connectionRecords.values()) {
if (record.isTLS) tlsConnections++;
else nonTlsConnections++;
if (record.hasKeepAlive) keepAliveConnections++;
if (record.usingNetworkProxy) networkProxyConnections++;
if (record.usingNetworkProxy) httpProxyConnections++;
}
return {
@ -726,7 +726,7 @@ export class SmartProxy extends plugins.EventEmitter {
tlsConnections,
nonTlsConnections,
keepAliveConnections,
networkProxyConnections,
httpProxyConnections,
terminationStats,
acmeEnabled: !!this.certManager,
port80HandlerPort: this.certManager ? 80 : null,