Skip to content

Commit f6306d5

Browse files
committed
Update PR 901 to add --websocket flag
1 parent d2f27cf commit f6306d5

File tree

2 files changed

+215
-6
lines changed

2 files changed

+215
-6
lines changed

lib/http-server.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -194,17 +194,12 @@ function HttpServer(options) {
194194
// if passphrase is set, shim must be used as union does not support
195195
? require('./shims/https-server-shim')(serverOptions)
196196
: union.createServer(serverOptions);
197-
if (proxy && options.websocket) {
198-
this.server.on('upgrade', function (request, socket, head) {
199-
proxy.ws(request, socket, head);
200-
});
201-
}
202197

203198
if (options.timeout !== undefined) {
204199
this.server.setTimeout(options.timeout);
205200
}
206201

207-
if (typeof options.proxy === 'string') {
202+
if (typeof options.proxy === 'string' && options.websocket) {
208203
this.server.on('upgrade', function (request, socket, head) {
209204
proxy.ws(request, socket, head, {
210205
target: options.proxy,

test/websocket-proxy.test.js

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
const test = require('tap').test
2+
const path = require('path')
3+
const http = require('http')
4+
const httpServer = require('../lib/http-server')
5+
const WebSocket = require('ws')
6+
7+
// Prevent errors from being swallowed
8+
process.on('uncaughtException', console.error)
9+
10+
test('websocket proxy functionality', (t) => {
11+
new Promise((resolve) => {
12+
// Create a target server that will handle websocket connections
13+
const targetServer = http.createServer()
14+
const targetWss = new WebSocket.Server({ server: targetServer })
15+
16+
targetWss.on('connection', (ws) => {
17+
ws.on('message', (message) => {
18+
// Echo the message back
19+
ws.send(`Echo: ${message}`)
20+
})
21+
})
22+
23+
targetServer.listen(0, () => {
24+
const targetPort = targetServer.address().port
25+
const targetUrl = `http://localhost:${targetPort}`
26+
27+
// Create http-server with websocket proxy enabled
28+
const proxyServer = httpServer.createServer({
29+
proxy: targetUrl,
30+
websocket: true,
31+
root: path.join(__dirname, 'fixtures')
32+
})
33+
34+
proxyServer.listen(0, async () => {
35+
const proxyPort = proxyServer.server.address().port
36+
const proxyUrl = `http://localhost:${proxyPort}`
37+
38+
try {
39+
// Test 1: Verify websocket proxy is enabled when both proxy and websocket options are set
40+
t.ok(proxyServer.server.listeners('upgrade').length > 0, 'upgrade event listener should be registered')
41+
42+
// Test 2: Test websocket connection through proxy
43+
await new Promise((resolve, reject) => {
44+
const ws = new WebSocket(`ws://localhost:${proxyPort}`)
45+
46+
ws.on('open', () => {
47+
t.pass('websocket connection should be established through proxy')
48+
49+
// Send a test message
50+
ws.send('Hello WebSocket!')
51+
})
52+
53+
ws.on('message', (data) => {
54+
t.equal(data.toString(), 'Echo: Hello WebSocket!', 'should receive echoed message')
55+
ws.close()
56+
})
57+
58+
ws.on('close', () => {
59+
t.pass('websocket connection should close properly')
60+
resolve()
61+
})
62+
63+
ws.on('error', (err) => {
64+
t.fail(`websocket error: ${err.message}`)
65+
reject(err)
66+
})
67+
68+
// Set timeout to prevent hanging
69+
setTimeout(() => {
70+
ws.close()
71+
reject(new Error('WebSocket test timeout'))
72+
}, 5000)
73+
})
74+
75+
} catch (err) {
76+
t.fail(`websocket proxy test failed: ${err.message}`)
77+
} finally {
78+
proxyServer.close()
79+
targetServer.close()
80+
resolve()
81+
}
82+
})
83+
})
84+
})
85+
.then(() => t.end())
86+
.catch(err => {
87+
t.fail(err.toString())
88+
t.end()
89+
})
90+
})
91+
92+
test('websocket proxy without proxy configuration', (t) => {
93+
new Promise((resolve) => {
94+
// Create http-server with websocket enabled but no proxy
95+
const server = httpServer.createServer({
96+
websocket: true,
97+
root: path.join(__dirname, 'fixtures')
98+
})
99+
100+
server.listen(0, () => {
101+
try {
102+
// Test: Verify no upgrade event listener is registered when proxy is not set
103+
t.equal(server.server.listeners('upgrade').length, 0, 'no upgrade event listener should be registered when proxy is not set')
104+
t.pass('websocket option should be ignored when proxy is not configured')
105+
} catch (err) {
106+
t.fail(`test failed: ${err.message}`)
107+
} finally {
108+
server.close()
109+
resolve()
110+
}
111+
})
112+
})
113+
.then(() => t.end())
114+
.catch(err => {
115+
t.fail(err.toString())
116+
t.end()
117+
})
118+
})
119+
120+
test('ensure websocket proxy is not enabled when \'websocket\' is not set', (t) => {
121+
new Promise((resolve) => {
122+
// Create a target server that will handle websocket connections
123+
const targetServer = http.createServer()
124+
const targetWss = new WebSocket.Server({ server: targetServer })
125+
126+
targetWss.on('connection', (ws) => {
127+
ws.on('message', (message) => {
128+
// Echo the message back
129+
ws.send(`Echo: ${message}`)
130+
})
131+
})
132+
133+
targetServer.listen(0, () => {
134+
const targetPort = targetServer.address().port
135+
const targetUrl = `http://localhost:${targetPort}`
136+
137+
const proxyServer = httpServer.createServer({
138+
proxy: targetUrl,
139+
root: path.join(__dirname, 'fixtures')
140+
})
141+
try {
142+
t.equal(proxyServer.server.listeners('upgrade').length, 0, 'no upgrade event listener should be registered when websocket is not set')
143+
} catch (err) {
144+
t.fail(`test failed: ${err.message}`)
145+
} finally {
146+
proxyServer.close()
147+
targetServer.close()
148+
resolve()
149+
}
150+
})
151+
})
152+
.then(() => t.end())
153+
.catch(err => {
154+
t.fail(err.toString())
155+
t.end()
156+
})
157+
});
158+
159+
test('websocket proxy error handling', (t) => {
160+
new Promise((resolve) => {
161+
// Create http-server with invalid proxy target
162+
const proxyServer = httpServer.createServer({
163+
proxy: 'http://localhost:99999', // Invalid port
164+
websocket: true,
165+
root: path.join(__dirname, 'fixtures')
166+
})
167+
168+
proxyServer.listen(0, async () => {
169+
const proxyPort = proxyServer.server.address().port
170+
171+
try {
172+
// Test: Verify websocket proxy handles connection errors gracefully
173+
t.ok(proxyServer.server.listeners('upgrade').length > 0, 'upgrade event listener should be registered even with invalid proxy')
174+
175+
// Test websocket connection to invalid proxy target
176+
await new Promise((resolve, reject) => {
177+
const ws = new WebSocket(`ws://localhost:${proxyPort}`)
178+
179+
ws.on('open', () => {
180+
t.fail('websocket should not connect to invalid proxy target')
181+
ws.close()
182+
resolve()
183+
})
184+
185+
ws.on('error', (err) => {
186+
t.pass('websocket should error when proxy target is invalid')
187+
resolve() // This is expected
188+
})
189+
190+
ws.on('close', () => {
191+
t.pass('websocket should close on error')
192+
resolve()
193+
})
194+
195+
setTimeout(() => {
196+
ws.close()
197+
resolve() // Timeout is acceptable for this test
198+
}, 2000)
199+
})
200+
201+
} catch (err) {
202+
t.fail(`websocket proxy error handling test failed: ${err.message}`)
203+
} finally {
204+
proxyServer.close()
205+
resolve()
206+
}
207+
})
208+
})
209+
.then(() => t.end())
210+
.catch(err => {
211+
t.fail(err.toString())
212+
t.end()
213+
})
214+
})

0 commit comments

Comments
 (0)