@@ -3,7 +3,7 @@ import { Buffer } from 'node:buffer';
33import type dns from 'node:dns' ;
44import { EventEmitter } from 'node:events' ;
55import http from 'node:http' ;
6- import type https from 'node:https' ;
6+ import https from 'node:https' ;
77import type net from 'node:net' ;
88import { URL } from 'node:url' ;
99import util from 'node:util' ;
@@ -19,7 +19,7 @@ import type { HandlerOpts as ForwardOpts } from './forward';
1919import { forward } from './forward' ;
2020import { forwardSocks } from './forward_socks' ;
2121import { RequestError } from './request_error' ;
22- import type { Socket } from './socket' ;
22+ import type { Socket , TLSSocket } from './socket' ;
2323import { badGatewayStatusCodes } from './statuses' ;
2424import { getTargetStats } from './utils/count_target_bytes' ;
2525import { nodeify } from './utils/nodeify' ;
@@ -41,10 +41,23 @@ export const SOCKS_PROTOCOLS = ['socks:', 'socks4:', 'socks4a:', 'socks5:', 'soc
4141const DEFAULT_AUTH_REALM = 'ProxyChain' ;
4242const DEFAULT_PROXY_SERVER_PORT = 8000 ;
4343
44+ const HTTPS_DEFAULT_OPTIONS = {
45+ // Disable TLS 1.0 and 1.1 (deprecated, insecure).
46+ // All other TLS settings use Node.js defaults for cipher selection (automatically updated).
47+ minVersion : 'TLSv1.2' ,
48+ } as const ;
49+
50+ /**
51+ * Connection statistics for bandwidth tracking.
52+ */
4453export type ConnectionStats = {
54+ // Bytes sent by proxy to client.
4555 srcTxBytes : number ;
56+ // Bytes received by proxy from client.
4657 srcRxBytes : number ;
58+ // Bytes sent by proxy to target.
4759 trgTxBytes : number | null ;
60+ // Bytes received by proxy from target.
4861 trgRxBytes : number | null ;
4962} ;
5063
@@ -96,10 +109,31 @@ export type PrepareRequestFunctionResult = {
96109type Promisable < T > = T | Promise < T > ;
97110export type PrepareRequestFunction = ( opts : PrepareRequestFunctionOpts ) => Promisable < undefined | PrepareRequestFunctionResult > ;
98111
112+ type ServerOptionsBase = {
113+ port ?: number ;
114+ host ?: string ;
115+ prepareRequestFunction ?: PrepareRequestFunction ;
116+ verbose ?: boolean ;
117+ authRealm ?: unknown ;
118+ } ;
119+
120+ export type HttpServerOptions = ServerOptionsBase & {
121+ serverType ?: 'http' ;
122+ } ;
123+
124+ export type HttpsServerOptions = ServerOptionsBase & {
125+ serverType : 'https' ;
126+ httpsOptions : https . ServerOptions ;
127+ } ;
128+
129+ export type ServerOptions = HttpServerOptions | HttpsServerOptions ;
130+
99131/**
100132 * Represents the proxy server.
101133 * It emits the 'requestFailed' event on unexpected request errors, with the following parameter `{ error, request }`.
102134 * It emits the 'connectionClosed' event when connection to proxy server is closed, with parameter `{ connectionId, stats }`.
135+ * It emits the 'tlsError' event on TLS handshake failures (HTTPS servers only), with parameter `{ error, socket }`.
136+ * with parameter `{ connectionId, reason, hasParent, parentType }`.
103137 */
104138export class Server extends EventEmitter {
105139 port : number ;
@@ -112,7 +146,9 @@ export class Server extends EventEmitter {
112146
113147 verbose : boolean ;
114148
115- server : http . Server ;
149+ server : http . Server | https . Server ;
150+
151+ serverType : 'http' | 'https' ;
116152
117153 lastHandlerId : number ;
118154
@@ -124,6 +160,9 @@ export class Server extends EventEmitter {
124160 * Initializes a new instance of Server class.
125161 * @param options
126162 * @param [options.port] Port where the server will listen. By default 8000.
163+ * @param [options.serverType] Type of server to create: 'http' or 'https'. By default 'http'.
164+ * @param [options.httpsOptions] HTTPS server options (required when serverType is 'https').
165+ * Accepts standard Node.js https.ServerOptions including key, cert, ca, passphrase, etc.
127166 * @param [options.prepareRequestFunction] Custom function to authenticate proxy requests,
128167 * provide URL to upstream proxy or potentially provide a function that generates a custom response to HTTP requests.
129168 * It accepts a single parameter which is an object:
@@ -154,13 +193,7 @@ export class Server extends EventEmitter {
154193 * @param [options.authRealm] Realm used in the Proxy-Authenticate header and also in the 'Server' HTTP header. By default it's `ProxyChain`.
155194 * @param [options.verbose] If true, the server will output logs
156195 */
157- constructor ( options : {
158- port ?: number ,
159- host ?: string ,
160- prepareRequestFunction ?: PrepareRequestFunction ,
161- verbose ?: boolean ,
162- authRealm ?: unknown ,
163- } = { } ) {
196+ constructor ( options : ServerOptions = { } ) {
164197 super ( ) ;
165198
166199 if ( options . port === undefined || options . port === null ) {
@@ -174,11 +207,43 @@ export class Server extends EventEmitter {
174207 this . authRealm = options . authRealm || DEFAULT_AUTH_REALM ;
175208 this . verbose = ! ! options . verbose ;
176209
177- this . server = http . createServer ( ) ;
210+ // Keep legacy behavior (http) as default behavior.
211+ this . serverType = options . serverType === 'https' ? 'https' : 'http' ;
212+
213+ if ( options . serverType === 'https' ) {
214+ if ( ! options . httpsOptions ) {
215+ throw new Error ( 'httpsOptions is required when serverType is "https"' ) ;
216+ }
217+
218+ // Apply secure TLS defaults (user options can override).
219+ const effectiveOptions : https . ServerOptions = {
220+ ...HTTPS_DEFAULT_OPTIONS ,
221+ honorCipherOrder : true ,
222+ ...options . httpsOptions ,
223+ } ;
224+
225+ this . server = https . createServer ( effectiveOptions ) ;
226+ } else {
227+ this . server = http . createServer ( ) ;
228+ }
229+
230+ // Attach common event handlers (same for both HTTP and HTTPS).
178231 this . server . on ( 'clientError' , this . onClientError . bind ( this ) ) ;
179232 this . server . on ( 'request' , this . onRequest . bind ( this ) ) ;
180233 this . server . on ( 'connect' , this . onConnect . bind ( this ) ) ;
181- this . server . on ( 'connection' , this . onConnection . bind ( this ) ) ;
234+
235+ // Attach connection tracking based on server type.
236+ // Only listen to one connection event to avoid double registration.
237+ if ( this . serverType === 'https' ) {
238+ // For HTTPS: Track only post-TLS-handshake sockets (secureConnection).
239+ // This ensures we track the TLS-wrapped socket with correct bytesRead/bytesWritten.
240+ this . server . on ( 'secureConnection' , this . onConnection . bind ( this ) ) ;
241+ // Handle TLS handshake errors to prevent server crashes.
242+ this . server . on ( 'tlsClientError' , this . onTLSClientError . bind ( this ) ) ;
243+ } else {
244+ // For HTTP: Track raw TCP sockets (connection).
245+ this . server . on ( 'connection' , this . onConnection . bind ( this ) ) ;
246+ }
182247
183248 this . lastHandlerId = 0 ;
184249 this . stats = {
@@ -189,6 +254,29 @@ export class Server extends EventEmitter {
189254 this . connections = new Map ( ) ;
190255 }
191256
257+ /**
258+ * Handles TLS handshake errors for HTTPS servers.
259+ * Without this handler, unhandled TLS errors can crash the server.
260+ * Common errors: ECONNRESET, ERR_SSL_SSLV3_ALERT_CERTIFICATE_UNKNOWN,
261+ * ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION, ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE
262+ */
263+ onTLSClientError ( err : NodeJS . ErrnoException , tlsSocket : TLSSocket ) : void {
264+ const connectionId = ( tlsSocket as TLSSocket ) . proxyChainId ;
265+ this . log ( connectionId , `TLS handshake failed: ${ err . message } ` ) ;
266+
267+ // Emit event in first place before any return statement.
268+ this . emit ( 'tlsError' , { error : err , socket : tlsSocket } ) ;
269+
270+ // If connection already reset or socket not writable, nothing more to do.
271+ if ( err . code === 'ECONNRESET' || ! tlsSocket . writable ) {
272+ return ;
273+ }
274+
275+ // TLS handshake failed before HTTP, cannot send HTTP response.
276+ // Destroy the socket to clean up.
277+ tlsSocket . destroy ( err ) ;
278+ }
279+
192280 log ( connectionId : unknown , str : string ) : void {
193281 if ( this . verbose ) {
194282 const logPrefix = connectionId != null ? `${ String ( connectionId ) } | ` : '' ;
0 commit comments