@furystack/rest-service 10.0.28 → 10.1.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.
- package/README.md +249 -12
- package/esm/api-manager.d.ts +1 -3
- package/esm/api-manager.d.ts.map +1 -1
- package/esm/api-manager.js +4 -6
- package/esm/api-manager.js.map +1 -1
- package/esm/header-processor.d.ts +39 -0
- package/esm/header-processor.d.ts.map +1 -0
- package/esm/header-processor.js +113 -0
- package/esm/header-processor.js.map +1 -0
- package/esm/header-processor.spec.d.ts +2 -0
- package/esm/header-processor.spec.d.ts.map +1 -0
- package/esm/header-processor.spec.js +420 -0
- package/esm/header-processor.spec.js.map +1 -0
- package/esm/helpers.d.ts +69 -1
- package/esm/helpers.d.ts.map +1 -1
- package/esm/helpers.js +70 -1
- package/esm/helpers.js.map +1 -1
- package/esm/helpers.spec.js +21 -5
- package/esm/helpers.spec.js.map +1 -1
- package/esm/http-proxy-handler.d.ts +53 -0
- package/esm/http-proxy-handler.d.ts.map +1 -0
- package/esm/http-proxy-handler.js +179 -0
- package/esm/http-proxy-handler.js.map +1 -0
- package/esm/http-user-context.d.ts +4 -4
- package/esm/http-user-context.d.ts.map +1 -1
- package/esm/http-user-context.js +4 -4
- package/esm/http-user-context.js.map +1 -1
- package/esm/index.d.ts +1 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +1 -0
- package/esm/index.js.map +1 -1
- package/esm/path-processor.d.ts +33 -0
- package/esm/path-processor.d.ts.map +1 -0
- package/esm/path-processor.js +58 -0
- package/esm/path-processor.js.map +1 -0
- package/esm/path-processor.spec.d.ts +2 -0
- package/esm/path-processor.spec.d.ts.map +1 -0
- package/esm/path-processor.spec.js +256 -0
- package/esm/path-processor.spec.js.map +1 -0
- package/esm/proxy-manager.d.ts +52 -0
- package/esm/proxy-manager.d.ts.map +1 -0
- package/esm/proxy-manager.js +84 -0
- package/esm/proxy-manager.js.map +1 -0
- package/esm/proxy-manager.spec.d.ts +2 -0
- package/esm/proxy-manager.spec.d.ts.map +1 -0
- package/esm/proxy-manager.spec.js +1781 -0
- package/esm/proxy-manager.spec.js.map +1 -0
- package/esm/server-manager.d.ts +7 -0
- package/esm/server-manager.d.ts.map +1 -1
- package/esm/server-manager.js +12 -0
- package/esm/server-manager.js.map +1 -1
- package/esm/static-server-manager.d.ts.map +1 -1
- package/esm/static-server-manager.js +5 -7
- package/esm/static-server-manager.js.map +1 -1
- package/esm/websocket-proxy-handler.d.ts +44 -0
- package/esm/websocket-proxy-handler.d.ts.map +1 -0
- package/esm/websocket-proxy-handler.js +157 -0
- package/esm/websocket-proxy-handler.js.map +1 -0
- package/package.json +11 -9
- package/src/api-manager.ts +5 -15
- package/src/header-processor.spec.ts +514 -0
- package/src/header-processor.ts +140 -0
- package/src/helpers.spec.ts +23 -5
- package/src/helpers.ts +72 -1
- package/src/http-proxy-handler.ts +215 -0
- package/src/http-user-context.ts +6 -6
- package/src/index.ts +1 -0
- package/src/path-processor.spec.ts +318 -0
- package/src/path-processor.ts +69 -0
- package/src/proxy-manager.spec.ts +2094 -0
- package/src/proxy-manager.ts +101 -0
- package/src/server-manager.ts +19 -0
- package/src/static-server-manager.ts +5 -7
- package/src/websocket-proxy-handler.ts +204 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Injectable, Injected } from '@furystack/inject'
|
|
2
|
+
import { EventHub, PathHelper } from '@furystack/utils'
|
|
3
|
+
import type { IncomingMessage, OutgoingHttpHeaders, ServerResponse } from 'http'
|
|
4
|
+
import type { Duplex } from 'stream'
|
|
5
|
+
import { HttpProxyHandler } from './http-proxy-handler.js'
|
|
6
|
+
import { PathProcessor } from './path-processor.js'
|
|
7
|
+
import { ServerManager } from './server-manager.js'
|
|
8
|
+
import { WebSocketProxyHandler } from './websocket-proxy-handler.js'
|
|
9
|
+
|
|
10
|
+
export interface ProxyOptions {
|
|
11
|
+
sourceBaseUrl: string
|
|
12
|
+
targetBaseUrl: string
|
|
13
|
+
pathRewrite?: (sourcePath: string) => string
|
|
14
|
+
sourceHostName?: string
|
|
15
|
+
sourcePort: number
|
|
16
|
+
headers?: (originalHeaders: OutgoingHttpHeaders) => OutgoingHttpHeaders
|
|
17
|
+
cookies?: (originalCookies: string[]) => string[]
|
|
18
|
+
responseCookies?: (responseCookies: string[]) => string[]
|
|
19
|
+
timeout?: number
|
|
20
|
+
enableWebsockets?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Manages HTTP and WebSocket proxy configurations and routing
|
|
25
|
+
*/
|
|
26
|
+
@Injectable({ lifetime: 'singleton' })
|
|
27
|
+
export class ProxyManager extends EventHub<{
|
|
28
|
+
onProxyFailed: { from: string; to: string; error: unknown }
|
|
29
|
+
onWebSocketProxyFailed: { from: string; to: string; error: unknown }
|
|
30
|
+
}> {
|
|
31
|
+
@Injected(ServerManager)
|
|
32
|
+
declare private readonly serverManager: ServerManager
|
|
33
|
+
|
|
34
|
+
private readonly pathProcessor = new PathProcessor()
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Creates a function that determines if a request should be handled by this proxy
|
|
38
|
+
*/
|
|
39
|
+
public shouldExec =
|
|
40
|
+
(sourceBaseUrl: string) =>
|
|
41
|
+
({ req }: { req: Pick<IncomingMessage, 'url' | 'method'> }) =>
|
|
42
|
+
req.url ? PathHelper.matchesBaseUrl(req.url, sourceBaseUrl) : false
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates an HTTP request handler for the proxy
|
|
46
|
+
*/
|
|
47
|
+
private createRequestHandler(options: ProxyOptions) {
|
|
48
|
+
const handler = new HttpProxyHandler({
|
|
49
|
+
sourceBaseUrl: options.sourceBaseUrl,
|
|
50
|
+
targetBaseUrl: options.targetBaseUrl,
|
|
51
|
+
pathRewrite: options.pathRewrite,
|
|
52
|
+
headers: options.headers,
|
|
53
|
+
cookies: options.cookies,
|
|
54
|
+
responseCookies: options.responseCookies,
|
|
55
|
+
timeout: options.timeout,
|
|
56
|
+
onError: (error) => this.emit('onProxyFailed', error),
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
return async ({ req, res }: { req: IncomingMessage; res: ServerResponse }) => {
|
|
60
|
+
await handler.handle(req, res)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Creates a WebSocket upgrade handler for the proxy
|
|
66
|
+
*/
|
|
67
|
+
private createUpgradeHandler(options: ProxyOptions) {
|
|
68
|
+
const handler = new WebSocketProxyHandler({
|
|
69
|
+
sourceBaseUrl: options.sourceBaseUrl,
|
|
70
|
+
targetBaseUrl: options.targetBaseUrl,
|
|
71
|
+
pathRewrite: options.pathRewrite,
|
|
72
|
+
headers: options.headers,
|
|
73
|
+
timeout: options.timeout,
|
|
74
|
+
onError: (error) => this.emit('onWebSocketProxyFailed', error),
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
return async ({ req, socket, head }: { req: IncomingMessage; socket: Duplex; head: Buffer }) => {
|
|
78
|
+
await handler.handle(req, socket, head)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Adds a new proxy configuration
|
|
84
|
+
* @throws Error if targetBaseUrl is invalid or uses non-HTTP/HTTPS protocol
|
|
85
|
+
*/
|
|
86
|
+
public async addProxy(options: ProxyOptions): Promise<void> {
|
|
87
|
+
// Validate targetBaseUrl format
|
|
88
|
+
const url = this.pathProcessor.validateUrl(options.targetBaseUrl, 'targetBaseUrl')
|
|
89
|
+
this.pathProcessor.validateHttpProtocol(url)
|
|
90
|
+
|
|
91
|
+
const server = await this.serverManager.getOrCreate({ hostName: options.sourceHostName, port: options.sourcePort })
|
|
92
|
+
|
|
93
|
+
const api = {
|
|
94
|
+
shouldExec: this.shouldExec(options.sourceBaseUrl),
|
|
95
|
+
onRequest: this.createRequestHandler(options),
|
|
96
|
+
...(options.enableWebsockets ? { onUpgrade: this.createUpgradeHandler(options) } : {}),
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
server.apis.push(api)
|
|
100
|
+
}
|
|
101
|
+
}
|
package/src/server-manager.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { IncomingMessage, Server, ServerResponse } from 'http'
|
|
|
4
4
|
import { createServer } from 'http'
|
|
5
5
|
import type { Socket } from 'net'
|
|
6
6
|
import { Lock } from 'semaphore-async-await'
|
|
7
|
+
import type { Duplex } from 'stream'
|
|
7
8
|
|
|
8
9
|
export interface ServerOptions {
|
|
9
10
|
hostName?: string
|
|
@@ -15,9 +16,16 @@ export interface OnRequest {
|
|
|
15
16
|
res: ServerResponse
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
export interface OnUpgrade {
|
|
20
|
+
req: IncomingMessage
|
|
21
|
+
socket: Duplex
|
|
22
|
+
head: Buffer
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
export interface ServerApi {
|
|
19
26
|
shouldExec: (options: OnRequest) => boolean
|
|
20
27
|
onRequest: (options: OnRequest) => Promise<void>
|
|
28
|
+
onUpgrade?: (options: OnUpgrade) => Promise<void>
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
export interface ServerRecord {
|
|
@@ -79,6 +87,17 @@ export class ServerManager
|
|
|
79
87
|
res.destroy()
|
|
80
88
|
}
|
|
81
89
|
})
|
|
90
|
+
server.on('upgrade', (req, socket, head) => {
|
|
91
|
+
const apiMatch = apis.find((api) => api.shouldExec({ req, res: {} as ServerResponse }))
|
|
92
|
+
if (apiMatch?.onUpgrade) {
|
|
93
|
+
apiMatch.onUpgrade({ req, socket, head }).catch((error) => {
|
|
94
|
+
this.emit('onRequestFailed', [error, req, {} as ServerResponse])
|
|
95
|
+
socket.destroy()
|
|
96
|
+
})
|
|
97
|
+
} else {
|
|
98
|
+
socket.destroy()
|
|
99
|
+
}
|
|
100
|
+
})
|
|
82
101
|
server.on('connection', this.onConnection)
|
|
83
102
|
server.on('listening', () => resolve())
|
|
84
103
|
server.on('error', (err) => reject(err))
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Injectable, Injected } from '@furystack/inject'
|
|
2
|
+
import { PathHelper } from '@furystack/utils'
|
|
2
3
|
import { createReadStream } from 'fs'
|
|
3
4
|
import { access, stat } from 'fs/promises'
|
|
4
5
|
import type { IncomingMessage, OutgoingHttpHeaders, ServerResponse } from 'http'
|
|
5
|
-
import { getMimeForFile } from './mime-types.js'
|
|
6
6
|
import { join, normalize, sep } from 'path'
|
|
7
|
+
import { getMimeForFile } from './mime-types.js'
|
|
7
8
|
import { ServerManager } from './server-manager.js'
|
|
8
9
|
|
|
9
10
|
export interface StaticServerOptions {
|
|
@@ -47,11 +48,7 @@ export class StaticServerManager {
|
|
|
47
48
|
public shouldExec =
|
|
48
49
|
(baseUrl: string) =>
|
|
49
50
|
({ req }: { req: Pick<IncomingMessage, 'url' | 'method'> }) =>
|
|
50
|
-
req.url &&
|
|
51
|
-
req.method?.toUpperCase() === 'GET' &&
|
|
52
|
-
(req.url === baseUrl || req.url.startsWith(baseUrl[baseUrl.length - 1] === '/' ? baseUrl : `${baseUrl}/`))
|
|
53
|
-
? true
|
|
54
|
-
: false
|
|
51
|
+
req.url && req.method?.toUpperCase() === 'GET' && PathHelper.matchesBaseUrl(req.url, baseUrl) ? true : false
|
|
55
52
|
|
|
56
53
|
private onRequest = ({
|
|
57
54
|
path,
|
|
@@ -65,7 +62,8 @@ export class StaticServerManager {
|
|
|
65
62
|
headers?: OutgoingHttpHeaders
|
|
66
63
|
}) => {
|
|
67
64
|
return async ({ req, res }: { req: IncomingMessage; res: ServerResponse }) => {
|
|
68
|
-
const
|
|
65
|
+
const extractedPath = PathHelper.extractPath(req.url as string, baseUrl)
|
|
66
|
+
const filePath = (extractedPath || '/').replaceAll('/', sep)
|
|
69
67
|
const fullPath = normalize(join(path, filePath))
|
|
70
68
|
|
|
71
69
|
try {
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import type { IncomingMessage, OutgoingHttpHeaders } from 'http'
|
|
2
|
+
import { request as httpRequest } from 'http'
|
|
3
|
+
import { request as httpsRequest } from 'https'
|
|
4
|
+
import type { Duplex } from 'stream'
|
|
5
|
+
import { HeaderProcessor } from './header-processor.js'
|
|
6
|
+
import { PathProcessor } from './path-processor.js'
|
|
7
|
+
|
|
8
|
+
export interface WebSocketProxyOptions {
|
|
9
|
+
sourceBaseUrl: string
|
|
10
|
+
targetBaseUrl: string
|
|
11
|
+
pathRewrite?: (sourcePath: string) => string
|
|
12
|
+
headers?: (originalHeaders: OutgoingHttpHeaders) => OutgoingHttpHeaders
|
|
13
|
+
timeout?: number
|
|
14
|
+
onError?: (error: { from: string; to: string; error: unknown }) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Handles WebSocket upgrade proxying with bidirectional streaming
|
|
19
|
+
*/
|
|
20
|
+
export class WebSocketProxyHandler {
|
|
21
|
+
private readonly headerProcessor = new HeaderProcessor()
|
|
22
|
+
private readonly pathProcessor = new PathProcessor()
|
|
23
|
+
|
|
24
|
+
constructor(private readonly options: WebSocketProxyOptions) {}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Builds WebSocket-specific upgrade headers
|
|
28
|
+
*/
|
|
29
|
+
private buildUpgradeHeaders(
|
|
30
|
+
req: IncomingMessage,
|
|
31
|
+
targetHost: string,
|
|
32
|
+
finalHeaders: OutgoingHttpHeaders,
|
|
33
|
+
): Record<string, string> {
|
|
34
|
+
const upgradeHeaders = this.headerProcessor.convertHeadersToRecord(finalHeaders)
|
|
35
|
+
|
|
36
|
+
// Add required WebSocket upgrade headers
|
|
37
|
+
upgradeHeaders.Host = targetHost
|
|
38
|
+
upgradeHeaders.Connection = 'Upgrade'
|
|
39
|
+
upgradeHeaders.Upgrade = 'websocket'
|
|
40
|
+
upgradeHeaders['Sec-WebSocket-Version'] = req.headers['sec-websocket-version'] as string
|
|
41
|
+
upgradeHeaders['Sec-WebSocket-Key'] = req.headers['sec-websocket-key'] as string
|
|
42
|
+
|
|
43
|
+
// Add optional WebSocket headers
|
|
44
|
+
if (req.headers['sec-websocket-protocol']) {
|
|
45
|
+
upgradeHeaders['Sec-WebSocket-Protocol'] = req.headers['sec-websocket-protocol']
|
|
46
|
+
}
|
|
47
|
+
if (req.headers['sec-websocket-extensions']) {
|
|
48
|
+
upgradeHeaders['Sec-WebSocket-Extensions'] = req.headers['sec-websocket-extensions']
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return upgradeHeaders
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Writes the upgrade response headers to the client socket
|
|
56
|
+
*/
|
|
57
|
+
private writeUpgradeResponse(socket: Duplex, proxyRes: IncomingMessage): void {
|
|
58
|
+
const responseHeaders = [`HTTP/1.1 ${proxyRes.statusCode} ${proxyRes.statusMessage}`]
|
|
59
|
+
for (const [key, value] of Object.entries(proxyRes.headers)) {
|
|
60
|
+
if (Array.isArray(value)) {
|
|
61
|
+
value.forEach((v) => responseHeaders.push(`${key}: ${v}`))
|
|
62
|
+
} else {
|
|
63
|
+
responseHeaders.push(`${key}: ${value}`)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
responseHeaders.push('', '')
|
|
67
|
+
socket.write(responseHeaders.join('\r\n'))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Sets up bidirectional piping between client and target sockets with error handling
|
|
72
|
+
*/
|
|
73
|
+
private setupBidirectionalPipe(
|
|
74
|
+
clientSocket: Duplex,
|
|
75
|
+
proxySocket: Duplex,
|
|
76
|
+
clientHead: Buffer,
|
|
77
|
+
proxyHead: Buffer,
|
|
78
|
+
): void {
|
|
79
|
+
// Write initial data
|
|
80
|
+
if (proxyHead.length > 0) {
|
|
81
|
+
clientSocket.write(proxyHead)
|
|
82
|
+
}
|
|
83
|
+
if (clientHead.length > 0) {
|
|
84
|
+
proxySocket.write(clientHead)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Bidirectional pipe
|
|
88
|
+
proxySocket.pipe(clientSocket)
|
|
89
|
+
clientSocket.pipe(proxySocket)
|
|
90
|
+
|
|
91
|
+
// Handle errors and cleanup
|
|
92
|
+
const cleanup = () => {
|
|
93
|
+
proxySocket.destroy()
|
|
94
|
+
clientSocket.destroy()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const handleError = (error: Error) => {
|
|
98
|
+
if (this.options.onError) {
|
|
99
|
+
this.options.onError({
|
|
100
|
+
from: this.options.sourceBaseUrl,
|
|
101
|
+
to: this.options.targetBaseUrl,
|
|
102
|
+
error,
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
cleanup()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
proxySocket.on('error', handleError)
|
|
109
|
+
clientSocket.on('error', handleError)
|
|
110
|
+
|
|
111
|
+
proxySocket.on('close', () => {
|
|
112
|
+
clientSocket.destroy()
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
clientSocket.on('close', () => {
|
|
116
|
+
proxySocket.destroy()
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Handles WebSocket upgrade errors
|
|
122
|
+
*/
|
|
123
|
+
private handleUpgradeError(error: unknown, socket: Duplex): void {
|
|
124
|
+
if (this.options.onError) {
|
|
125
|
+
this.options.onError({
|
|
126
|
+
from: this.options.sourceBaseUrl,
|
|
127
|
+
to: this.options.targetBaseUrl,
|
|
128
|
+
error,
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
socket.destroy()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Main handler for WebSocket upgrade requests
|
|
136
|
+
*/
|
|
137
|
+
public async handle(req: IncomingMessage, socket: Duplex, head: Buffer): Promise<void> {
|
|
138
|
+
try {
|
|
139
|
+
// Build target URL
|
|
140
|
+
const targetUrl = this.pathProcessor.processUrl(
|
|
141
|
+
req.url as string,
|
|
142
|
+
this.options.sourceBaseUrl,
|
|
143
|
+
this.options.targetBaseUrl,
|
|
144
|
+
this.options.pathRewrite,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
const parsedTargetUrl = new URL(targetUrl)
|
|
148
|
+
|
|
149
|
+
// Process headers
|
|
150
|
+
const originalHeaders: OutgoingHttpHeaders = {}
|
|
151
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
152
|
+
originalHeaders[key] = Array.isArray(value) ? value.join(', ') : value
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const filteredHeaders = this.headerProcessor.filterHeaders(originalHeaders)
|
|
156
|
+
const finalHeaders = this.options.headers ? this.options.headers(filteredHeaders) : filteredHeaders
|
|
157
|
+
|
|
158
|
+
// Build WebSocket upgrade headers
|
|
159
|
+
const upgradeHeaders = this.buildUpgradeHeaders(req, parsedTargetUrl.host, finalHeaders)
|
|
160
|
+
|
|
161
|
+
// Set up timeout
|
|
162
|
+
const timeoutMs = this.options.timeout ?? 30000
|
|
163
|
+
const timeoutId = setTimeout(() => {
|
|
164
|
+
socket.destroy()
|
|
165
|
+
}, timeoutMs)
|
|
166
|
+
|
|
167
|
+
// Create upgrade request to target server
|
|
168
|
+
const requestFn = parsedTargetUrl.protocol === 'https:' ? httpsRequest : httpRequest
|
|
169
|
+
const proxyReq = requestFn({
|
|
170
|
+
host: parsedTargetUrl.hostname,
|
|
171
|
+
port: parsedTargetUrl.port || (parsedTargetUrl.protocol === 'https:' ? 443 : 80),
|
|
172
|
+
path: parsedTargetUrl.pathname + parsedTargetUrl.search,
|
|
173
|
+
method: 'GET',
|
|
174
|
+
headers: upgradeHeaders,
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
proxyReq.on('upgrade', (proxyRes, proxySocket, proxyHead) => {
|
|
178
|
+
clearTimeout(timeoutId)
|
|
179
|
+
|
|
180
|
+
// Write the upgrade response to the client socket
|
|
181
|
+
this.writeUpgradeResponse(socket, proxyRes)
|
|
182
|
+
|
|
183
|
+
// Set up bidirectional piping
|
|
184
|
+
this.setupBidirectionalPipe(socket, proxySocket, head, proxyHead)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
proxyReq.on('error', (error) => {
|
|
188
|
+
clearTimeout(timeoutId)
|
|
189
|
+
this.handleUpgradeError(error, socket)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
proxyReq.on('timeout', () => {
|
|
193
|
+
clearTimeout(timeoutId)
|
|
194
|
+
const timeoutError = new Error('WebSocket upgrade timeout')
|
|
195
|
+
this.handleUpgradeError(timeoutError, socket)
|
|
196
|
+
proxyReq.destroy()
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
proxyReq.end()
|
|
200
|
+
} catch (error) {
|
|
201
|
+
this.handleUpgradeError(error, socket)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|