@@ -141,76 +141,9 @@ export class TunnelService extends EventEmitter {
141
141
}
142
142
143
143
private async validateTargetService ( target : string , port : number ) : Promise < boolean > {
144
- const maxRetries = 3 ;
145
- const retryDelay = 1000 ; // 1 second
146
-
147
- for ( let attempt = 1 ; attempt <= maxRetries ; attempt ++ ) {
148
- try {
149
- logger . info ( `Validating target service (attempt ${ attempt } /${ maxRetries } )` , {
150
- target,
151
- port
152
- } ) ;
153
-
154
- const isAvailable = await new Promise < boolean > ( ( resolve ) => {
155
- const socket = new net . Socket ( ) ;
156
-
157
- socket . setTimeout ( 2000 ) ; // 2 second timeout
158
-
159
- socket . on ( 'connect' , ( ) => {
160
- logger . info ( `Successfully connected to target service` , {
161
- target,
162
- port,
163
- attempt
164
- } ) ;
165
- socket . destroy ( ) ;
166
- resolve ( true ) ;
167
- } ) ;
168
-
169
- socket . on ( 'timeout' , ( ) => {
170
- logger . warn ( `Connection attempt timed out` , {
171
- target,
172
- port,
173
- attempt
174
- } ) ;
175
- socket . destroy ( ) ;
176
- resolve ( false ) ;
177
- } ) ;
178
-
179
- socket . on ( 'error' , ( err ) => {
180
- logger . warn ( `Connection attempt failed` , {
181
- target,
182
- port,
183
- attempt,
184
- error : err . message
185
- } ) ;
186
- socket . destroy ( ) ;
187
- resolve ( false ) ;
188
- } ) ;
189
-
190
- const host = target . replace ( / ^ h t t p s ? : \/ \/ / , '' ) . split ( ':' ) [ 0 ] ;
191
- logger . info ( `Attempting to connect to ${ host } :${ port } ` ) ;
192
- socket . connect ( port , host ) ;
193
- } ) ;
194
-
195
- if ( isAvailable ) {
196
- return true ;
197
- }
198
-
199
- if ( attempt < maxRetries ) {
200
- logger . info ( `Retrying connection after ${ retryDelay } ms...` ) ;
201
- await new Promise ( resolve => setTimeout ( resolve , retryDelay ) ) ;
202
- }
203
- } catch ( error ) {
204
- logger . error ( `Error during service validation` , {
205
- target,
206
- port,
207
- attempt,
208
- error
209
- } ) ;
210
- }
211
- }
212
-
213
- return false ;
144
+ // In a VM setup, we can't directly validate the client's local port
145
+ // Instead, we'll trust that the client has verified its local port
146
+ return true ;
214
147
}
215
148
216
149
public async proxyRequestWrapper ( req : IncomingMessage , res : ServerResponse ) : Promise < void > {
@@ -241,168 +174,74 @@ export class TunnelService extends EventEmitter {
241
174
}
242
175
243
176
try {
244
- const target = tunnelConfig . targetUrl || ( tunnelConfig . targetPort ? `http://localhost:${ tunnelConfig . targetPort } ` : undefined ) ;
245
- if ( ! target ) {
246
- logger . error ( `No target URL or port found for tunnel: ${ subdomain } ` , {
247
- tunnel : {
248
- subdomain : tunnelConfig . subdomain ,
249
- targetPort : tunnelConfig . targetPort ,
250
- targetUrl : tunnelConfig . targetUrl
251
- }
252
- } ) ;
253
- res . writeHead ( 502 ) ;
254
- res . end ( JSON . stringify ( { error : 'Tunnel target not configured' } ) ) ;
255
- return ;
256
- }
257
-
258
- // Validate target service availability
259
- const isAvailable = await this . validateTargetService ( target , tunnelConfig . targetPort ! ) ;
260
- if ( ! isAvailable ) {
261
- logger . error ( `Target service not available: ${ target } ` , {
262
- subdomain,
263
- port : tunnelConfig . targetPort
264
- } ) ;
265
- res . writeHead ( 502 ) ;
266
- res . end ( JSON . stringify ( {
267
- error : 'Target service not available' ,
268
- details : `Could not connect to service on port ${ tunnelConfig . targetPort } . Make sure your service is running.`
269
- } ) ) ;
270
- return ;
271
- }
177
+ // In a VM setup, we need to use WebSocket to forward the request to the client
178
+ const message = {
179
+ type : 'request' ,
180
+ method : req . method ,
181
+ path : req . url ,
182
+ headers : req . headers ,
183
+ body : await this . getRequestBody ( req )
184
+ } ;
272
185
273
- logger . info ( `Forwarding request to target: ${ target } ` , {
186
+ logger . info ( `Forwarding request via WebSocket ` , {
274
187
subdomain,
275
188
method : req . method ,
276
- url : req . url
189
+ url : req . url ,
190
+ targetPort : tunnelConfig . targetPort
277
191
} ) ;
278
-
279
- await this . handleProxyRequest ( req , res , target , tunnelConfig ) ;
280
- } catch ( error ) {
281
- logger . error ( 'Error in proxyRequest' , { error, subdomain } ) ;
282
- res . statusCode = 500 ;
283
- res . end ( JSON . stringify ( { error : 'Proxy request failed' } ) ) ;
284
- }
285
- }
286
192
287
- private async handleProxyRequest ( req : IncomingMessage , res : ServerResponse , target : string , tunnelConfig : TunnelConfig ) {
288
- try {
289
- const proxy = httpProxy . createProxyServer ( { } ) ;
290
-
291
- // Add error handler for proxy errors
292
- proxy . on ( 'error' , ( err : Error , req : IncomingMessage , res : ServerResponse ) => {
293
- logger . error ( 'Proxy error' , {
294
- error : err . message ,
295
- target,
296
- headers : req . headers ,
297
- method : req . method ,
298
- url : req . url ,
299
- stack : err . stack
300
- } ) ;
193
+ // Send the request to the client via WebSocket
194
+ tunnelConfig . ws . send ( JSON . stringify ( message ) ) ;
301
195
302
- // Handle specific error cases
303
- if ( err . message . includes ( 'ECONNREFUSED' ) ) {
304
- logger . error ( 'Target service not available' , {
305
- target,
306
- error : 'Connection refused' ,
307
- port : tunnelConfig . targetPort
308
- } ) ;
309
- res . writeHead ( 502 ) ;
310
- res . end ( JSON . stringify ( {
311
- error : 'Target service not available' ,
312
- details : `Connection refused to port ${ tunnelConfig . targetPort } . Make sure your service is running and listening on the correct port.`
313
- } ) ) ;
314
- return ;
315
- }
196
+ // Wait for the response from the client
197
+ const response = await this . waitForResponse ( tunnelConfig . ws ) ;
316
198
317
- if ( err . message . includes ( 'ECONNRESET' ) ) {
318
- logger . error ( 'Connection reset by target' , {
319
- target,
320
- error : 'Connection reset'
321
- } ) ;
322
- res . writeHead ( 504 ) ;
323
- res . end ( JSON . stringify ( {
324
- error : 'Connection reset' ,
325
- details : 'The target service unexpectedly closed the connection.'
326
- } ) ) ;
327
- return ;
328
- }
199
+ // Forward the response back to the original requester
200
+ res . writeHead ( response . statusCode , response . headers ) ;
201
+ res . end ( response . body ) ;
329
202
330
- if ( err . message . includes ( 'ETIMEDOUT' ) ) {
331
- logger . error ( 'Connection timed out' , {
332
- target,
333
- error : 'Timeout'
334
- } ) ;
335
- res . writeHead ( 504 ) ;
336
- res . end ( JSON . stringify ( {
337
- error : 'Gateway timeout' ,
338
- details : 'The target service took too long to respond.'
339
- } ) ) ;
340
- return ;
341
- }
203
+ } catch ( error ) {
204
+ logger . error ( 'Error in proxyRequest' , { error, subdomain } ) ;
205
+ res . statusCode = 502 ;
206
+ res . end ( JSON . stringify ( {
207
+ error : 'Bad Gateway' ,
208
+ details : 'Error communicating with the client tunnel'
209
+ } ) ) ;
210
+ }
211
+ }
342
212
343
- // Default error response
344
- res . writeHead ( 500 ) ;
345
- res . end ( JSON . stringify ( {
346
- error : 'Proxy error' ,
347
- details : err . message ,
348
- code : err . name
349
- } ) ) ;
213
+ private getRequestBody ( req : IncomingMessage ) : Promise < string > {
214
+ return new Promise ( ( resolve ) => {
215
+ let body = '' ;
216
+ req . on ( 'data' , chunk => {
217
+ body += chunk . toString ( ) ;
350
218
} ) ;
351
-
352
- proxy . web ( req , res , {
353
- target,
354
- secure : false ,
355
- changeOrigin : true ,
356
- selfHandleResponse : true ,
357
- ws : false // Disable WebSocket upgrade for HTTP requests
219
+ req . on ( 'end' , ( ) => {
220
+ resolve ( body ) ;
358
221
} ) ;
222
+ } ) ;
223
+ }
359
224
360
- proxy . on ( 'proxyRes' , ( proxyRes : IncomingMessage , req : IncomingMessage , res : ServerResponse ) => {
361
- const chunks : Buffer [ ] = [ ] ;
362
-
363
- proxyRes . on ( 'data' , ( chunk : Buffer ) => {
364
- chunks . push ( chunk ) ;
365
- } ) ;
366
-
367
- proxyRes . on ( 'end' , async ( ) => {
368
- try {
369
- const buffer = Buffer . concat ( chunks ) ;
370
- const encoding = proxyRes . headers [ 'content-encoding' ] ;
371
-
372
- // Copy all headers except content-length (let Node calculate it)
373
- Object . keys ( proxyRes . headers ) . forEach ( key => {
374
- if ( key . toLowerCase ( ) !== 'content-length' ) {
375
- res . setHeader ( key , proxyRes . headers [ key ] ! ) ;
376
- }
377
- } ) ;
378
-
379
- // Set status code
380
- res . statusCode = proxyRes . statusCode || 200 ;
381
-
382
- // Log response details
383
- logger . info ( 'Proxying response' , {
384
- statusCode : proxyRes . statusCode ,
385
- contentEncoding : encoding ,
386
- contentLength : buffer . length ,
387
- headers : proxyRes . headers ,
388
- target
389
- } ) ;
390
-
391
- // Send the response
392
- res . end ( buffer ) ;
393
-
394
- } catch ( error ) {
395
- logger . error ( 'Error processing proxy response' , { error, target } ) ;
396
- res . statusCode = 500 ;
397
- res . end ( JSON . stringify ( { error : 'Error processing response' } ) ) ;
225
+ private waitForResponse ( ws : WebSocket ) : Promise < any > {
226
+ return new Promise ( ( resolve , reject ) => {
227
+ const timeout = setTimeout ( ( ) => {
228
+ reject ( new Error ( 'Response timeout' ) ) ;
229
+ } , 30000 ) ; // 30 second timeout
230
+
231
+ const messageHandler = ( data : WebSocket . Data ) => {
232
+ try {
233
+ const response = JSON . parse ( data . toString ( ) ) ;
234
+ if ( response . type === 'response' ) {
235
+ clearTimeout ( timeout ) ;
236
+ ws . removeListener ( 'message' , messageHandler ) ;
237
+ resolve ( response ) ;
398
238
}
399
- } ) ;
400
- } ) ;
239
+ } catch ( error ) {
240
+ logger . error ( 'Error parsing response' , { error } ) ;
241
+ }
242
+ } ;
401
243
402
- } catch ( error ) {
403
- logger . error ( 'Error in handleProxyRequest' , { error, target } ) ;
404
- res . statusCode = 500 ;
405
- res . end ( JSON . stringify ( { error : 'Proxy request failed' } ) ) ;
406
- }
244
+ ws . on ( 'message' , messageHandler ) ;
245
+ } ) ;
407
246
}
408
247
}
0 commit comments