@push.rocks/smartproxy 10.2.0 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/common/port80-adapter.d.ts +11 -0
  3. package/dist_ts/common/port80-adapter.js +61 -0
  4. package/dist_ts/examples/forwarding-example.d.ts +1 -0
  5. package/dist_ts/examples/forwarding-example.js +96 -0
  6. package/dist_ts/index.d.ts +1 -0
  7. package/dist_ts/index.js +3 -1
  8. package/dist_ts/smartproxy/classes.pp.connectionhandler.js +179 -30
  9. package/dist_ts/smartproxy/classes.pp.domainconfigmanager.d.ts +39 -0
  10. package/dist_ts/smartproxy/classes.pp.domainconfigmanager.js +172 -20
  11. package/dist_ts/smartproxy/classes.pp.interfaces.d.ts +3 -11
  12. package/dist_ts/smartproxy/classes.pp.portrangemanager.js +17 -10
  13. package/dist_ts/smartproxy/classes.pp.securitymanager.d.ts +19 -2
  14. package/dist_ts/smartproxy/classes.pp.securitymanager.js +27 -4
  15. package/dist_ts/smartproxy/classes.pp.timeoutmanager.js +3 -3
  16. package/dist_ts/smartproxy/classes.smartproxy.js +45 -13
  17. package/dist_ts/smartproxy/forwarding/domain-config.d.ts +12 -0
  18. package/dist_ts/smartproxy/forwarding/domain-config.js +12 -0
  19. package/dist_ts/smartproxy/forwarding/domain-manager.d.ts +86 -0
  20. package/dist_ts/smartproxy/forwarding/domain-manager.js +241 -0
  21. package/dist_ts/smartproxy/forwarding/forwarding.factory.d.ts +24 -0
  22. package/dist_ts/smartproxy/forwarding/forwarding.factory.js +137 -0
  23. package/dist_ts/smartproxy/forwarding/forwarding.handler.d.ts +55 -0
  24. package/dist_ts/smartproxy/forwarding/forwarding.handler.js +94 -0
  25. package/dist_ts/smartproxy/forwarding/http.handler.d.ts +25 -0
  26. package/dist_ts/smartproxy/forwarding/http.handler.js +123 -0
  27. package/dist_ts/smartproxy/forwarding/https-passthrough.handler.d.ts +24 -0
  28. package/dist_ts/smartproxy/forwarding/https-passthrough.handler.js +154 -0
  29. package/dist_ts/smartproxy/forwarding/https-terminate-to-http.handler.d.ts +36 -0
  30. package/dist_ts/smartproxy/forwarding/https-terminate-to-http.handler.js +229 -0
  31. package/dist_ts/smartproxy/forwarding/https-terminate-to-https.handler.d.ts +35 -0
  32. package/dist_ts/smartproxy/forwarding/https-terminate-to-https.handler.js +254 -0
  33. package/dist_ts/smartproxy/forwarding/index.d.ts +16 -0
  34. package/dist_ts/smartproxy/forwarding/index.js +23 -0
  35. package/dist_ts/smartproxy/types/forwarding.types.d.ts +104 -0
  36. package/dist_ts/smartproxy/types/forwarding.types.js +50 -0
  37. package/package.json +2 -2
  38. package/readme.md +158 -8
  39. package/readme.plan.md +471 -42
  40. package/ts/00_commitinfo_data.ts +1 -1
  41. package/ts/common/port80-adapter.ts +87 -0
  42. package/ts/examples/forwarding-example.ts +128 -0
  43. package/ts/index.ts +3 -0
  44. package/ts/smartproxy/classes.pp.connectionhandler.ts +231 -44
  45. package/ts/smartproxy/classes.pp.domainconfigmanager.ts +198 -24
  46. package/ts/smartproxy/classes.pp.interfaces.ts +3 -11
  47. package/ts/smartproxy/classes.pp.portrangemanager.ts +17 -10
  48. package/ts/smartproxy/classes.pp.securitymanager.ts +29 -5
  49. package/ts/smartproxy/classes.pp.timeoutmanager.ts +3 -3
  50. package/ts/smartproxy/classes.smartproxy.ts +68 -15
  51. package/ts/smartproxy/forwarding/domain-config.ts +28 -0
  52. package/ts/smartproxy/forwarding/domain-manager.ts +283 -0
  53. package/ts/smartproxy/forwarding/forwarding.factory.ts +155 -0
  54. package/ts/smartproxy/forwarding/forwarding.handler.ts +127 -0
  55. package/ts/smartproxy/forwarding/http.handler.ts +140 -0
  56. package/ts/smartproxy/forwarding/https-passthrough.handler.ts +182 -0
  57. package/ts/smartproxy/forwarding/https-terminate-to-http.handler.ts +264 -0
  58. package/ts/smartproxy/forwarding/https-terminate-to-https.handler.ts +292 -0
  59. package/ts/smartproxy/forwarding/index.ts +52 -0
  60. package/ts/smartproxy/types/forwarding.types.ts +162 -0
@@ -0,0 +1,283 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import type { IDomainConfig } from './domain-config.js';
3
+ import type { IForwardingHandler } from '../types/forwarding.types.js';
4
+ import { ForwardingHandlerEvents } from '../types/forwarding.types.js';
5
+ import { ForwardingHandlerFactory } from './forwarding.factory.js';
6
+
7
+ /**
8
+ * Events emitted by the DomainManager
9
+ */
10
+ export enum DomainManagerEvents {
11
+ DOMAIN_ADDED = 'domain-added',
12
+ DOMAIN_REMOVED = 'domain-removed',
13
+ DOMAIN_MATCHED = 'domain-matched',
14
+ DOMAIN_MATCH_FAILED = 'domain-match-failed',
15
+ CERTIFICATE_NEEDED = 'certificate-needed',
16
+ CERTIFICATE_LOADED = 'certificate-loaded',
17
+ ERROR = 'error'
18
+ }
19
+
20
+ /**
21
+ * Manages domains and their forwarding handlers
22
+ */
23
+ export class DomainManager extends plugins.EventEmitter {
24
+ private domainConfigs: IDomainConfig[] = [];
25
+ private domainHandlers: Map<string, IForwardingHandler> = new Map();
26
+
27
+ /**
28
+ * Create a new DomainManager
29
+ * @param initialDomains Optional initial domain configurations
30
+ */
31
+ constructor(initialDomains?: IDomainConfig[]) {
32
+ super();
33
+
34
+ if (initialDomains) {
35
+ this.setDomainConfigs(initialDomains);
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Set or replace all domain configurations
41
+ * @param configs Array of domain configurations
42
+ */
43
+ public async setDomainConfigs(configs: IDomainConfig[]): Promise<void> {
44
+ // Clear existing handlers
45
+ this.domainHandlers.clear();
46
+
47
+ // Store new configurations
48
+ this.domainConfigs = [...configs];
49
+
50
+ // Initialize handlers for each domain
51
+ for (const config of this.domainConfigs) {
52
+ await this.createHandlersForDomain(config);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Add a new domain configuration
58
+ * @param config The domain configuration to add
59
+ */
60
+ public async addDomainConfig(config: IDomainConfig): Promise<void> {
61
+ // Check if any of these domains already exist
62
+ for (const domain of config.domains) {
63
+ if (this.domainHandlers.has(domain)) {
64
+ // Remove existing handler for this domain
65
+ this.domainHandlers.delete(domain);
66
+ }
67
+ }
68
+
69
+ // Add the new configuration
70
+ this.domainConfigs.push(config);
71
+
72
+ // Create handlers for the new domain
73
+ await this.createHandlersForDomain(config);
74
+
75
+ this.emit(DomainManagerEvents.DOMAIN_ADDED, {
76
+ domains: config.domains,
77
+ forwardingType: config.forwarding.type
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Remove a domain configuration
83
+ * @param domain The domain to remove
84
+ * @returns True if the domain was found and removed
85
+ */
86
+ public removeDomainConfig(domain: string): boolean {
87
+ // Find the config that includes this domain
88
+ const index = this.domainConfigs.findIndex(config =>
89
+ config.domains.includes(domain)
90
+ );
91
+
92
+ if (index === -1) {
93
+ return false;
94
+ }
95
+
96
+ // Get the config
97
+ const config = this.domainConfigs[index];
98
+
99
+ // Remove all handlers for this config
100
+ for (const domainName of config.domains) {
101
+ this.domainHandlers.delete(domainName);
102
+ }
103
+
104
+ // Remove the config
105
+ this.domainConfigs.splice(index, 1);
106
+
107
+ this.emit(DomainManagerEvents.DOMAIN_REMOVED, {
108
+ domains: config.domains
109
+ });
110
+
111
+ return true;
112
+ }
113
+
114
+ /**
115
+ * Find the handler for a domain
116
+ * @param domain The domain to find a handler for
117
+ * @returns The handler or undefined if no match
118
+ */
119
+ public findHandlerForDomain(domain: string): IForwardingHandler | undefined {
120
+ // Try exact match
121
+ if (this.domainHandlers.has(domain)) {
122
+ return this.domainHandlers.get(domain);
123
+ }
124
+
125
+ // Try wildcard matches
126
+ const wildcardHandler = this.findWildcardHandler(domain);
127
+ if (wildcardHandler) {
128
+ return wildcardHandler;
129
+ }
130
+
131
+ // No match found
132
+ return undefined;
133
+ }
134
+
135
+ /**
136
+ * Handle a connection for a domain
137
+ * @param domain The domain
138
+ * @param socket The client socket
139
+ * @returns True if the connection was handled
140
+ */
141
+ public handleConnection(domain: string, socket: plugins.net.Socket): boolean {
142
+ const handler = this.findHandlerForDomain(domain);
143
+
144
+ if (!handler) {
145
+ this.emit(DomainManagerEvents.DOMAIN_MATCH_FAILED, {
146
+ domain,
147
+ remoteAddress: socket.remoteAddress
148
+ });
149
+ return false;
150
+ }
151
+
152
+ this.emit(DomainManagerEvents.DOMAIN_MATCHED, {
153
+ domain,
154
+ handlerType: handler.constructor.name,
155
+ remoteAddress: socket.remoteAddress
156
+ });
157
+
158
+ // Handle the connection
159
+ handler.handleConnection(socket);
160
+ return true;
161
+ }
162
+
163
+ /**
164
+ * Handle an HTTP request for a domain
165
+ * @param domain The domain
166
+ * @param req The HTTP request
167
+ * @param res The HTTP response
168
+ * @returns True if the request was handled
169
+ */
170
+ public handleHttpRequest(domain: string, req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): boolean {
171
+ const handler = this.findHandlerForDomain(domain);
172
+
173
+ if (!handler) {
174
+ this.emit(DomainManagerEvents.DOMAIN_MATCH_FAILED, {
175
+ domain,
176
+ remoteAddress: req.socket.remoteAddress
177
+ });
178
+ return false;
179
+ }
180
+
181
+ this.emit(DomainManagerEvents.DOMAIN_MATCHED, {
182
+ domain,
183
+ handlerType: handler.constructor.name,
184
+ remoteAddress: req.socket.remoteAddress
185
+ });
186
+
187
+ // Handle the request
188
+ handler.handleHttpRequest(req, res);
189
+ return true;
190
+ }
191
+
192
+ /**
193
+ * Create handlers for a domain configuration
194
+ * @param config The domain configuration
195
+ */
196
+ private async createHandlersForDomain(config: IDomainConfig): Promise<void> {
197
+ try {
198
+ // Create a handler for this forwarding configuration
199
+ const handler = ForwardingHandlerFactory.createHandler(config.forwarding);
200
+
201
+ // Initialize the handler
202
+ await handler.initialize();
203
+
204
+ // Set up event forwarding
205
+ this.setupHandlerEvents(handler, config);
206
+
207
+ // Store the handler for each domain in the config
208
+ for (const domain of config.domains) {
209
+ this.domainHandlers.set(domain, handler);
210
+ }
211
+ } catch (error) {
212
+ this.emit(DomainManagerEvents.ERROR, {
213
+ domains: config.domains,
214
+ error: error instanceof Error ? error.message : String(error)
215
+ });
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Set up event forwarding from a handler
221
+ * @param handler The handler
222
+ * @param config The domain configuration for this handler
223
+ */
224
+ private setupHandlerEvents(handler: IForwardingHandler, config: IDomainConfig): void {
225
+ // Forward relevant events
226
+ handler.on(ForwardingHandlerEvents.CERTIFICATE_NEEDED, (data) => {
227
+ this.emit(DomainManagerEvents.CERTIFICATE_NEEDED, {
228
+ ...data,
229
+ domains: config.domains
230
+ });
231
+ });
232
+
233
+ handler.on(ForwardingHandlerEvents.CERTIFICATE_LOADED, (data) => {
234
+ this.emit(DomainManagerEvents.CERTIFICATE_LOADED, {
235
+ ...data,
236
+ domains: config.domains
237
+ });
238
+ });
239
+
240
+ handler.on(ForwardingHandlerEvents.ERROR, (data) => {
241
+ this.emit(DomainManagerEvents.ERROR, {
242
+ ...data,
243
+ domains: config.domains
244
+ });
245
+ });
246
+ }
247
+
248
+ /**
249
+ * Find a handler for a domain using wildcard matching
250
+ * @param domain The domain to find a handler for
251
+ * @returns The handler or undefined if no match
252
+ */
253
+ private findWildcardHandler(domain: string): IForwardingHandler | undefined {
254
+ // Exact match already checked in findHandlerForDomain
255
+
256
+ // Try subdomain wildcard (*.example.com)
257
+ if (domain.includes('.')) {
258
+ const parts = domain.split('.');
259
+ if (parts.length > 2) {
260
+ const wildcardDomain = `*.${parts.slice(1).join('.')}`;
261
+ if (this.domainHandlers.has(wildcardDomain)) {
262
+ return this.domainHandlers.get(wildcardDomain);
263
+ }
264
+ }
265
+ }
266
+
267
+ // Try full wildcard
268
+ if (this.domainHandlers.has('*')) {
269
+ return this.domainHandlers.get('*');
270
+ }
271
+
272
+ // No match found
273
+ return undefined;
274
+ }
275
+
276
+ /**
277
+ * Get all domain configurations
278
+ * @returns Array of domain configurations
279
+ */
280
+ public getDomainConfigs(): IDomainConfig[] {
281
+ return [...this.domainConfigs];
282
+ }
283
+ }
@@ -0,0 +1,155 @@
1
+ import type { IForwardConfig, IForwardingHandler } from '../types/forwarding.types.js';
2
+ import { HttpForwardingHandler } from './http.handler.js';
3
+ import { HttpsPassthroughHandler } from './https-passthrough.handler.js';
4
+ import { HttpsTerminateToHttpHandler } from './https-terminate-to-http.handler.js';
5
+ import { HttpsTerminateToHttpsHandler } from './https-terminate-to-https.handler.js';
6
+
7
+ /**
8
+ * Factory for creating forwarding handlers based on the configuration type
9
+ */
10
+ export class ForwardingHandlerFactory {
11
+ /**
12
+ * Create a forwarding handler based on the configuration
13
+ * @param config The forwarding configuration
14
+ * @returns The appropriate forwarding handler
15
+ */
16
+ public static createHandler(config: IForwardConfig): IForwardingHandler {
17
+ // Create the appropriate handler based on the forwarding type
18
+ switch (config.type) {
19
+ case 'http-only':
20
+ return new HttpForwardingHandler(config);
21
+
22
+ case 'https-passthrough':
23
+ return new HttpsPassthroughHandler(config);
24
+
25
+ case 'https-terminate-to-http':
26
+ return new HttpsTerminateToHttpHandler(config);
27
+
28
+ case 'https-terminate-to-https':
29
+ return new HttpsTerminateToHttpsHandler(config);
30
+
31
+ default:
32
+ // Type system should prevent this, but just in case:
33
+ throw new Error(`Unknown forwarding type: ${(config as any).type}`);
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Apply default values to a forwarding configuration based on its type
39
+ * @param config The original forwarding configuration
40
+ * @returns A configuration with defaults applied
41
+ */
42
+ public static applyDefaults(config: IForwardConfig): IForwardConfig {
43
+ // Create a deep copy of the configuration
44
+ const result: IForwardConfig = JSON.parse(JSON.stringify(config));
45
+
46
+ // Apply defaults based on forwarding type
47
+ switch (config.type) {
48
+ case 'http-only':
49
+ // Set defaults for HTTP-only mode
50
+ result.http = {
51
+ enabled: true,
52
+ ...config.http
53
+ };
54
+ break;
55
+
56
+ case 'https-passthrough':
57
+ // Set defaults for HTTPS passthrough
58
+ result.https = {
59
+ forwardSni: true,
60
+ ...config.https
61
+ };
62
+ // SNI forwarding doesn't do HTTP
63
+ result.http = {
64
+ enabled: false,
65
+ ...config.http
66
+ };
67
+ break;
68
+
69
+ case 'https-terminate-to-http':
70
+ // Set defaults for HTTPS termination to HTTP
71
+ result.https = {
72
+ ...config.https
73
+ };
74
+ // Support HTTP access by default in this mode
75
+ result.http = {
76
+ enabled: true,
77
+ redirectToHttps: true,
78
+ ...config.http
79
+ };
80
+ // Enable ACME by default
81
+ result.acme = {
82
+ enabled: true,
83
+ maintenance: true,
84
+ ...config.acme
85
+ };
86
+ break;
87
+
88
+ case 'https-terminate-to-https':
89
+ // Similar to terminate-to-http but with different target handling
90
+ result.https = {
91
+ ...config.https
92
+ };
93
+ result.http = {
94
+ enabled: true,
95
+ redirectToHttps: true,
96
+ ...config.http
97
+ };
98
+ result.acme = {
99
+ enabled: true,
100
+ maintenance: true,
101
+ ...config.acme
102
+ };
103
+ break;
104
+ }
105
+
106
+ return result;
107
+ }
108
+
109
+ /**
110
+ * Validate a forwarding configuration
111
+ * @param config The configuration to validate
112
+ * @throws Error if the configuration is invalid
113
+ */
114
+ public static validateConfig(config: IForwardConfig): void {
115
+ // Validate common properties
116
+ if (!config.target) {
117
+ throw new Error('Forwarding configuration must include a target');
118
+ }
119
+
120
+ if (!config.target.host || (Array.isArray(config.target.host) && config.target.host.length === 0)) {
121
+ throw new Error('Target must include a host or array of hosts');
122
+ }
123
+
124
+ if (!config.target.port || config.target.port <= 0 || config.target.port > 65535) {
125
+ throw new Error('Target must include a valid port (1-65535)');
126
+ }
127
+
128
+ // Type-specific validation
129
+ switch (config.type) {
130
+ case 'http-only':
131
+ // HTTP-only needs http.enabled to be true
132
+ if (config.http?.enabled === false) {
133
+ throw new Error('HTTP-only forwarding must have HTTP enabled');
134
+ }
135
+ break;
136
+
137
+ case 'https-passthrough':
138
+ // HTTPS passthrough doesn't support HTTP
139
+ if (config.http?.enabled === true) {
140
+ throw new Error('HTTPS passthrough does not support HTTP');
141
+ }
142
+
143
+ // HTTPS passthrough doesn't work with ACME
144
+ if (config.acme?.enabled === true) {
145
+ throw new Error('HTTPS passthrough does not support ACME');
146
+ }
147
+ break;
148
+
149
+ case 'https-terminate-to-http':
150
+ case 'https-terminate-to-https':
151
+ // These modes support all options, nothing specific to validate
152
+ break;
153
+ }
154
+ }
155
+ }
@@ -0,0 +1,127 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import type {
3
+ IForwardConfig,
4
+ IForwardingHandler
5
+ } from '../types/forwarding.types.js';
6
+ import { ForwardingHandlerEvents } from '../types/forwarding.types.js';
7
+
8
+ /**
9
+ * Base class for all forwarding handlers
10
+ */
11
+ export abstract class ForwardingHandler extends plugins.EventEmitter implements IForwardingHandler {
12
+ /**
13
+ * Create a new ForwardingHandler
14
+ * @param config The forwarding configuration
15
+ */
16
+ constructor(protected config: IForwardConfig) {
17
+ super();
18
+ }
19
+
20
+ /**
21
+ * Initialize the handler
22
+ * Base implementation does nothing, subclasses should override as needed
23
+ */
24
+ public async initialize(): Promise<void> {
25
+ // Base implementation - no initialization needed
26
+ }
27
+
28
+ /**
29
+ * Handle a new socket connection
30
+ * @param socket The incoming socket connection
31
+ */
32
+ public abstract handleConnection(socket: plugins.net.Socket): void;
33
+
34
+ /**
35
+ * Handle an HTTP request
36
+ * @param req The HTTP request
37
+ * @param res The HTTP response
38
+ */
39
+ public abstract handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void;
40
+
41
+ /**
42
+ * Get a target from the configuration, supporting round-robin selection
43
+ * @returns A resolved target object with host and port
44
+ */
45
+ protected getTargetFromConfig(): { host: string, port: number } {
46
+ const { target } = this.config;
47
+
48
+ // Handle round-robin host selection
49
+ if (Array.isArray(target.host)) {
50
+ if (target.host.length === 0) {
51
+ throw new Error('No target hosts specified');
52
+ }
53
+
54
+ // Simple round-robin selection
55
+ const randomIndex = Math.floor(Math.random() * target.host.length);
56
+ return {
57
+ host: target.host[randomIndex],
58
+ port: target.port
59
+ };
60
+ }
61
+
62
+ // Single host
63
+ return {
64
+ host: target.host,
65
+ port: target.port
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Redirect an HTTP request to HTTPS
71
+ * @param req The HTTP request
72
+ * @param res The HTTP response
73
+ */
74
+ protected redirectToHttps(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void {
75
+ const host = req.headers.host || '';
76
+ const path = req.url || '/';
77
+ const redirectUrl = `https://${host}${path}`;
78
+
79
+ res.writeHead(301, {
80
+ 'Location': redirectUrl,
81
+ 'Cache-Control': 'no-cache'
82
+ });
83
+ res.end(`Redirecting to ${redirectUrl}`);
84
+
85
+ this.emit(ForwardingHandlerEvents.HTTP_RESPONSE, {
86
+ statusCode: 301,
87
+ headers: { 'Location': redirectUrl },
88
+ size: 0
89
+ });
90
+ }
91
+
92
+ /**
93
+ * Apply custom headers from configuration
94
+ * @param headers The original headers
95
+ * @param variables Variables to replace in the headers
96
+ * @returns The headers with custom values applied
97
+ */
98
+ protected applyCustomHeaders(
99
+ headers: Record<string, string | string[] | undefined>,
100
+ variables: Record<string, string>
101
+ ): Record<string, string | string[] | undefined> {
102
+ const customHeaders = this.config.advanced?.headers || {};
103
+ const result = { ...headers };
104
+
105
+ // Apply custom headers with variable substitution
106
+ for (const [key, value] of Object.entries(customHeaders)) {
107
+ let processedValue = value;
108
+
109
+ // Replace variables in the header value
110
+ for (const [varName, varValue] of Object.entries(variables)) {
111
+ processedValue = processedValue.replace(`{${varName}}`, varValue);
112
+ }
113
+
114
+ result[key] = processedValue;
115
+ }
116
+
117
+ return result;
118
+ }
119
+
120
+ /**
121
+ * Get the timeout for this connection from configuration
122
+ * @returns Timeout in milliseconds
123
+ */
124
+ protected getTimeout(): number {
125
+ return this.config.advanced?.timeout || 60000; // Default: 60 seconds
126
+ }
127
+ }
@@ -0,0 +1,140 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import { ForwardingHandler } from './forwarding.handler.js';
3
+ import type { IForwardConfig } from '../types/forwarding.types.js';
4
+ import { ForwardingHandlerEvents } from '../types/forwarding.types.js';
5
+
6
+ /**
7
+ * Handler for HTTP-only forwarding
8
+ */
9
+ export class HttpForwardingHandler extends ForwardingHandler {
10
+ /**
11
+ * Create a new HTTP forwarding handler
12
+ * @param config The forwarding configuration
13
+ */
14
+ constructor(config: IForwardConfig) {
15
+ super(config);
16
+
17
+ // Validate that this is an HTTP-only configuration
18
+ if (config.type !== 'http-only') {
19
+ throw new Error(`Invalid configuration type for HttpForwardingHandler: ${config.type}`);
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Handle a raw socket connection
25
+ * HTTP handler doesn't do much with raw sockets as it mainly processes
26
+ * parsed HTTP requests
27
+ */
28
+ public handleConnection(socket: plugins.net.Socket): void {
29
+ // For HTTP, we mainly handle parsed requests, but we can still set up
30
+ // some basic connection tracking
31
+ const remoteAddress = socket.remoteAddress || 'unknown';
32
+
33
+ socket.on('close', (hadError) => {
34
+ this.emit(ForwardingHandlerEvents.DISCONNECTED, {
35
+ remoteAddress,
36
+ hadError
37
+ });
38
+ });
39
+
40
+ socket.on('error', (error) => {
41
+ this.emit(ForwardingHandlerEvents.ERROR, {
42
+ remoteAddress,
43
+ error: error.message
44
+ });
45
+ });
46
+
47
+ this.emit(ForwardingHandlerEvents.CONNECTED, {
48
+ remoteAddress
49
+ });
50
+ }
51
+
52
+ /**
53
+ * Handle an HTTP request
54
+ * @param req The HTTP request
55
+ * @param res The HTTP response
56
+ */
57
+ public handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void {
58
+ // Get the target from configuration
59
+ const target = this.getTargetFromConfig();
60
+
61
+ // Create a custom headers object with variables for substitution
62
+ const variables = {
63
+ clientIp: req.socket.remoteAddress || 'unknown'
64
+ };
65
+
66
+ // Prepare headers, merging with any custom headers from config
67
+ const headers = this.applyCustomHeaders(req.headers, variables);
68
+
69
+ // Create the proxy request options
70
+ const options = {
71
+ hostname: target.host,
72
+ port: target.port,
73
+ path: req.url,
74
+ method: req.method,
75
+ headers
76
+ };
77
+
78
+ // Create the proxy request
79
+ const proxyReq = plugins.http.request(options, (proxyRes) => {
80
+ // Copy status code and headers from the proxied response
81
+ res.writeHead(proxyRes.statusCode || 500, proxyRes.headers);
82
+
83
+ // Pipe the proxy response to the client response
84
+ proxyRes.pipe(res);
85
+
86
+ // Track bytes for logging
87
+ let responseSize = 0;
88
+ proxyRes.on('data', (chunk) => {
89
+ responseSize += chunk.length;
90
+ });
91
+
92
+ proxyRes.on('end', () => {
93
+ this.emit(ForwardingHandlerEvents.HTTP_RESPONSE, {
94
+ statusCode: proxyRes.statusCode,
95
+ headers: proxyRes.headers,
96
+ size: responseSize
97
+ });
98
+ });
99
+ });
100
+
101
+ // Handle errors in the proxy request
102
+ proxyReq.on('error', (error) => {
103
+ this.emit(ForwardingHandlerEvents.ERROR, {
104
+ remoteAddress: req.socket.remoteAddress,
105
+ error: `Proxy request error: ${error.message}`
106
+ });
107
+
108
+ // Send an error response if headers haven't been sent yet
109
+ if (!res.headersSent) {
110
+ res.writeHead(502, { 'Content-Type': 'text/plain' });
111
+ res.end(`Error forwarding request: ${error.message}`);
112
+ } else {
113
+ // Just end the response if headers have already been sent
114
+ res.end();
115
+ }
116
+ });
117
+
118
+ // Track request details for logging
119
+ let requestSize = 0;
120
+ req.on('data', (chunk) => {
121
+ requestSize += chunk.length;
122
+ });
123
+
124
+ // Log the request
125
+ this.emit(ForwardingHandlerEvents.HTTP_REQUEST, {
126
+ method: req.method,
127
+ url: req.url,
128
+ headers: req.headers,
129
+ remoteAddress: req.socket.remoteAddress,
130
+ target: `${target.host}:${target.port}`
131
+ });
132
+
133
+ // Pipe the client request to the proxy request
134
+ if (req.readable) {
135
+ req.pipe(proxyReq);
136
+ } else {
137
+ proxyReq.end();
138
+ }
139
+ }
140
+ }