Skip to content

Commit fd0f568

Browse files
fix(websocket): handle errors in handleUpgrade (#823)
* fix(websocket): handle errors in handleUpgrade * test(websocket): add test for error handling * fix(websocket): handle sockets in error-response-plugin --------- Co-authored-by: Steven Chim <[email protected]>
1 parent e94087e commit fd0f568

File tree

3 files changed

+62
-18
lines changed

3 files changed

+62
-18
lines changed

src/http-proxy-middleware.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,16 @@ export class HttpProxyMiddleware<TReq, TRes> {
9898
};
9999

100100
private handleUpgrade = async (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => {
101-
if (this.shouldProxy(this.proxyOptions.pathFilter, req)) {
102-
const activeProxyOptions = await this.prepareProxyRequest(req);
103-
this.proxy.ws(req, socket, head, activeProxyOptions);
104-
debug('server upgrade event received. Proxying WebSocket');
101+
try {
102+
if (this.shouldProxy(this.proxyOptions.pathFilter, req)) {
103+
const activeProxyOptions = await this.prepareProxyRequest(req);
104+
this.proxy.ws(req, socket, head, activeProxyOptions);
105+
debug('server upgrade event received. Proxying WebSocket');
106+
}
107+
} catch (err) {
108+
// This error does not include the URL as the fourth argument as we won't
109+
// have the URL if `this.prepareProxyRequest` throws an error.
110+
this.proxy.emit('error', err, req, socket);
105111
}
106112
};
107113

+20-6
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,33 @@
1+
import type * as http from 'http';
2+
import type { Socket } from 'net';
13
import { getStatusCode } from '../../status-code';
24
import { Plugin } from '../../types';
35

6+
function isResponseLike(obj: any): obj is http.ServerResponse {
7+
return obj && typeof obj.writeHead === 'function';
8+
}
9+
10+
function isSocketLike(obj: any): obj is Socket {
11+
return obj && typeof obj.write === 'function' && !('writeHead' in obj);
12+
}
13+
414
export const errorResponsePlugin: Plugin = (proxyServer, options) => {
515
proxyServer.on('error', (err, req, res, target?) => {
616
// Re-throw error. Not recoverable since req & res are empty.
717
if (!req && !res) {
818
throw err; // "Error: Must provide a proper URL as target"
919
}
1020

11-
if ('writeHead' in res && !res.headersSent) {
12-
const statusCode = getStatusCode((err as unknown as any).code);
13-
res.writeHead(statusCode);
14-
}
21+
if (isResponseLike(res)) {
22+
if (!res.headersSent) {
23+
const statusCode = getStatusCode((err as unknown as any).code);
24+
res.writeHead(statusCode);
25+
}
1526

16-
const host = req.headers && req.headers.host;
17-
res.end(`Error occurred while trying to proxy: ${host}${req.url}`);
27+
const host = req.headers && req.headers.host;
28+
res.end(`Error occurred while trying to proxy: ${host}${req.url}`);
29+
} else if (isSocketLike(res)) {
30+
res.destroy();
31+
}
1832
});
1933
};

test/e2e/websocket.spec.ts

+32-8
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,14 @@ describe('E2E WebSocket proxy', () => {
9797

9898
describe('with router and pathRewrite', () => {
9999
beforeEach(() => {
100-
// override
101-
proxyServer = createApp(
100+
const proxyMiddleware = createProxyMiddleware({
102101
// cSpell:ignore notworkinghost
103-
createProxyMiddleware({
104-
target: 'ws://notworkinghost:6789',
105-
router: { '/socket': `ws://localhost:${WS_SERVER_PORT}` },
106-
pathRewrite: { '^/socket': '' },
107-
}),
108-
).listen(SERVER_PORT);
102+
target: 'ws://notworkinghost:6789',
103+
router: { '/socket': `ws://localhost:${WS_SERVER_PORT}` },
104+
pathRewrite: { '^/socket': '' },
105+
});
106+
107+
proxyServer = createApp(proxyMiddleware).listen(SERVER_PORT);
109108

110109
proxyServer.on('upgrade', proxyMiddleware.upgrade);
111110
});
@@ -124,4 +123,29 @@ describe('E2E WebSocket proxy', () => {
124123
ws.send('foobar');
125124
});
126125
});
126+
127+
describe('with error in router', () => {
128+
beforeEach(() => {
129+
const proxyMiddleware = createProxyMiddleware({
130+
// cSpell:ignore notworkinghost
131+
target: `http://notworkinghost:6789`,
132+
router: async () => {
133+
throw new Error('error');
134+
},
135+
});
136+
137+
proxyServer = createApp(proxyMiddleware).listen(SERVER_PORT);
138+
139+
proxyServer.on('upgrade', proxyMiddleware.upgrade);
140+
});
141+
142+
it('should handle error', (done) => {
143+
ws = new WebSocket(`ws://localhost:${SERVER_PORT}/socket`);
144+
145+
ws.on('error', (err) => {
146+
expect(err).toBeTruthy();
147+
done();
148+
});
149+
});
150+
});
127151
});

0 commit comments

Comments
 (0)