@@ -58,37 +58,76 @@ function proxyWebSockets (source, target) {
58
58
target . on ( 'unexpected-response' , ( ) => close ( 1011 , 'unexpected response' ) )
59
59
}
60
60
61
- function setupWebSocketProxy ( fastify , options , rewritePrefix ) {
62
- const server = new WebSocket . Server ( {
63
- server : fastify . server ,
64
- ...options . wsServerOptions
65
- } )
66
-
67
- fastify . addHook ( 'onClose' , ( instance , done ) => server . close ( done ) )
68
-
69
- // To be able to close the HTTP server,
70
- // all WebSocket clients need to be disconnected.
71
- // Fastify is missing a pre-close event, or the ability to
72
- // add a hook before the server.close call. We need to resort
73
- // to monkeypatching for now.
74
- const oldClose = fastify . server . close
75
- fastify . server . close = function ( done ) {
76
- for ( const client of server . clients ) {
77
- client . close ( )
61
+ class WebSocketProxy {
62
+ constructor ( fastify , wsServerOptions ) {
63
+ this . logger = fastify . log
64
+
65
+ const wss = new WebSocket . Server ( {
66
+ server : fastify . server ,
67
+ ...wsServerOptions
68
+ } )
69
+
70
+ // To be able to close the HTTP server,
71
+ // all WebSocket clients need to be disconnected.
72
+ // Fastify is missing a pre-close event, or the ability to
73
+ // add a hook before the server.close call. We need to resort
74
+ // to monkeypatching for now.
75
+ const oldClose = fastify . server . close
76
+ fastify . server . close = function ( done ) {
77
+ for ( const client of wss . clients ) {
78
+ client . close ( )
79
+ }
80
+ oldClose . call ( this , done )
78
81
}
79
- oldClose . call ( this , done )
82
+
83
+ wss . on ( 'error' , ( err ) => {
84
+ this . logger . error ( err )
85
+ } )
86
+
87
+ wss . on ( 'connection' , this . handleConnection . bind ( this ) )
88
+
89
+ this . wss = wss
90
+ this . prefixList = [ ]
80
91
}
81
92
82
- server . on ( 'error' , ( err ) => {
83
- fastify . log . error ( err )
84
- } )
93
+ close ( done ) {
94
+ this . wss . close ( done )
95
+ }
96
+
97
+ addUpstream ( prefix , rewritePrefix , upstream , wsClientOptions ) {
98
+ this . prefixList . push ( {
99
+ prefix : new URL ( prefix , 'ws://127.0.0.1' ) . pathname ,
100
+ rewritePrefix,
101
+ upstream : convertUrlToWebSocket ( upstream ) ,
102
+ wsClientOptions
103
+ } )
104
+
105
+ // sort by decreasing prefix length, so that findUpstreamUrl() does longest prefix match
106
+ this . prefixList . sort ( ( a , b ) => b . prefix . length - a . prefix . length )
107
+ }
85
108
86
- server . on ( 'connection' , ( source , request ) => {
87
- if ( fastify . prefix && ! request . url . startsWith ( fastify . prefix ) ) {
88
- fastify . log . debug ( { url : request . url } , 'not matching prefix' )
109
+ findUpstream ( request ) {
110
+ const source = new URL ( request . url , 'ws://127.0.0.1' )
111
+
112
+ for ( const { prefix, rewritePrefix, upstream, wsClientOptions } of this . prefixList ) {
113
+ if ( source . pathname . startsWith ( prefix ) ) {
114
+ const target = new URL ( source . pathname . replace ( prefix , rewritePrefix ) , upstream )
115
+ target . search = source . search
116
+ return { target, wsClientOptions }
117
+ }
118
+ }
119
+
120
+ return undefined
121
+ }
122
+
123
+ handleConnection ( source , request ) {
124
+ const upstream = this . findUpstream ( request )
125
+ if ( ! upstream ) {
126
+ this . logger . debug ( { url : request . url } , 'not matching prefix' )
89
127
source . close ( )
90
128
return
91
129
}
130
+ const { target : url , wsClientOptions } = upstream
92
131
93
132
const subprotocols = [ ]
94
133
if ( source . protocol ) {
@@ -98,31 +137,32 @@ function setupWebSocketProxy (fastify, options, rewritePrefix) {
98
137
let optionsWs = { }
99
138
if ( request . headers . cookie ) {
100
139
const headers = { cookie : request . headers . cookie }
101
- optionsWs = { ...options . wsClientOptions , headers }
140
+ optionsWs = { ...wsClientOptions , headers }
102
141
} else {
103
- optionsWs = options . wsClientOptions
142
+ optionsWs = wsClientOptions
104
143
}
105
144
106
- const url = createWebSocketUrl ( request )
107
-
108
145
const target = new WebSocket ( url , subprotocols , optionsWs )
109
-
110
- fastify . log . debug ( { url : url . href } , 'proxy websocket' )
146
+ this . logger . debug ( { url : url . href } , 'proxy websocket' )
111
147
proxyWebSockets ( source , target )
112
- } )
113
-
114
- function createWebSocketUrl ( request ) {
115
- const source = new URL ( request . url , 'ws://127.0.0.1' )
116
-
117
- const target = new URL (
118
- source . pathname . replace ( fastify . prefix , rewritePrefix ) ,
119
- convertUrlToWebSocket ( options . upstream )
120
- )
148
+ }
149
+ }
121
150
122
- target . search = source . search
151
+ const httpWss = new WeakMap ( ) // http.Server => WebSocketProxy
123
152
124
- return target
153
+ function setupWebSocketProxy ( fastify , options , rewritePrefix ) {
154
+ let wsProxy = httpWss . get ( fastify . server )
155
+ if ( ! wsProxy ) {
156
+ wsProxy = new WebSocketProxy ( fastify , options . wsServerOptions )
157
+ httpWss . set ( fastify . server , wsProxy )
158
+
159
+ fastify . addHook ( 'onClose' , ( instance , done ) => {
160
+ httpWss . delete ( fastify . server )
161
+ wsProxy . close ( done )
162
+ } )
125
163
}
164
+
165
+ wsProxy . addUpstream ( fastify . prefix , rewritePrefix , options . upstream , options . wsClientOptions )
126
166
}
127
167
128
168
function generateRewritePrefix ( prefix = '' , opts ) {
0 commit comments