Skip to content

Commit 7bf1898

Browse files
geroplroboquat
authored andcommitted
[server] WsHandler: Take only upgrade first matching websocket route
1 parent 562ee68 commit 7bf1898

File tree

2 files changed

+37
-17
lines changed

2 files changed

+37
-17
lines changed

components/server/src/express/ws-handler.ts

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { log } from '@gitpod/gitpod-protocol/lib/util/logging';
1515
import { increaseHttpRequestCounter } from '../prometheus-metrics';
1616

1717
export type HttpServer = http.Server | https.Server;
18-
export type Route = string | RegExp;
18+
export type RouteMatcher = string | RegExp;
1919
export type MaybePromise = Promise<any> | any;
2020

2121
export interface WsNextFunction {
@@ -31,10 +31,15 @@ export type WsHandler = WsRequestHandler | WsErrorHandler;
3131

3232
export type WsConnectionFilter = websocket.VerifyClientCallbackAsync | websocket.VerifyClientCallbackSync;
3333

34+
interface Route {
35+
matcher: RouteMatcher;
36+
handler: (ws: websocket, req: express.Request) => void;
37+
}
3438

3539
export class WsExpressHandler {
3640

3741
protected readonly wss: websocket.Server;
42+
protected readonly routes: Route[] = [];
3843

3944
constructor(
4045
protected readonly httpServer: HttpServer,
@@ -50,11 +55,12 @@ export class WsExpressHandler {
5055
clientTracking: false,
5156
});
5257
this.wss.on('error', (err) => {
53-
log.error('Websocket error', err, { ws: this.wss });
58+
log.error('websocket server error', err, { wss: this.wss });
5459
});
60+
this.httpServer.on('upgrade', (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => this.onUpgrade(req, socket, head));
5561
}
5662

57-
ws(route: Route, handler: (ws: websocket, request: express.Request) => void, ...handlers: WsHandler[]): void {
63+
ws(matcher: RouteMatcher, handler: (ws: websocket, request: express.Request) => void, ...handlers: WsHandler[]): void {
5864
const stack = WsLayer.createStack(...handlers);
5965
const dispatch = (ws: websocket, request: express.Request) => {
6066
handler(ws, request);
@@ -68,24 +74,32 @@ export class WsExpressHandler {
6874
});
6975
}
7076

71-
this.httpServer.on('upgrade', (request: http.IncomingMessage, socket: net.Socket, head: Buffer) => {
72-
const pathname = request.url ? url.parse(request.url).pathname : undefined;
73-
if (this.matches(route, pathname)) {
74-
this.wss.handleUpgrade(request, socket, head, ws => {
75-
if (ws.readyState === ws.OPEN) {
76-
dispatch(ws, request as express.Request);
77-
} else {
78-
ws.on('open', () => dispatch(ws, request as express.Request));
79-
}
80-
});
77+
this.routes.push({
78+
matcher,
79+
handler: (ws, req) => {
80+
if (ws.readyState === ws.OPEN) {
81+
dispatch(ws, req);
82+
} else {
83+
ws.on('open', () => dispatch(ws, req));
84+
}
8185
}
8286
});
8387
}
8488

85-
protected matches(route: Route, pathname: string | undefined | null): boolean {
86-
if (route instanceof RegExp) {
87-
return !!pathname && route.test(pathname);
89+
protected onUpgrade(request: http.IncomingMessage, socket: net.Socket, head: Buffer) {
90+
const pathname = request.url ? url.parse(request.url).pathname : undefined;
91+
for (const route of this.routes) {
92+
if (this.matches(route.matcher, pathname)) {
93+
this.wss.handleUpgrade(request, socket, head, (ws) => route.handler(ws, request as express.Request));
94+
return; // take the first match and stop
95+
}
96+
}
97+
}
98+
99+
protected matches(matcher: RouteMatcher, pathname: string | undefined | null): boolean {
100+
if (matcher instanceof RegExp) {
101+
return !!pathname && matcher.test(pathname);
88102
}
89-
return pathname === route;
103+
return pathname === matcher;
90104
}
91105
}

components/server/src/server.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,12 @@ export class Server<C extends GitpodClient, S extends GitpodServer> {
204204
}, handleError, wsPingPongHandler.handler(), (ws: ws, req: express.Request) => {
205205
websocketConnectionHandler.onConnection((req as any).wsConnection, req);
206206
});
207+
wsHandler.ws(/.*/, (ws, request) => {
208+
// fallthrough case
209+
// note: this is suboptimal as we upgrade and than terminate the request. But we're not sure this is a problem at all, so we start out with this
210+
log.warn("websocket path not matching", { path: request.path })
211+
ws.terminate();
212+
});
207213

208214
// start ws heartbeat/ping-pong
209215
wsPingPongHandler.start();

0 commit comments

Comments
 (0)