@fastify/http-proxy
Advanced tools
Comparing version 8.1.0 to 8.2.0
116
index.js
@@ -61,33 +61,72 @@ 'use strict' | ||
function setupWebSocketProxy (fastify, options, rewritePrefix) { | ||
const server = new WebSocket.Server({ | ||
server: fastify.server, | ||
...options.wsServerOptions | ||
}) | ||
class WebSocketProxy { | ||
constructor (fastify, wsServerOptions) { | ||
this.logger = fastify.log | ||
fastify.addHook('onClose', (instance, done) => server.close(done)) | ||
const wss = new WebSocket.Server({ | ||
server: fastify.server, | ||
...wsServerOptions | ||
}) | ||
// To be able to close the HTTP server, | ||
// all WebSocket clients need to be disconnected. | ||
// Fastify is missing a pre-close event, or the ability to | ||
// add a hook before the server.close call. We need to resort | ||
// to monkeypatching for now. | ||
const oldClose = fastify.server.close | ||
fastify.server.close = function (done) { | ||
for (const client of server.clients) { | ||
client.close() | ||
// To be able to close the HTTP server, | ||
// all WebSocket clients need to be disconnected. | ||
// Fastify is missing a pre-close event, or the ability to | ||
// add a hook before the server.close call. We need to resort | ||
// to monkeypatching for now. | ||
const oldClose = fastify.server.close | ||
fastify.server.close = function (done) { | ||
for (const client of wss.clients) { | ||
client.close() | ||
} | ||
oldClose.call(this, done) | ||
} | ||
oldClose.call(this, done) | ||
wss.on('error', (err) => { | ||
this.logger.error(err) | ||
}) | ||
wss.on('connection', this.handleConnection.bind(this)) | ||
this.wss = wss | ||
this.prefixList = [] | ||
} | ||
server.on('error', (err) => { | ||
fastify.log.error(err) | ||
}) | ||
close (done) { | ||
this.wss.close(done) | ||
} | ||
server.on('connection', (source, request) => { | ||
if (fastify.prefix && !request.url.startsWith(fastify.prefix)) { | ||
fastify.log.debug({ url: request.url }, 'not matching prefix') | ||
addUpstream (prefix, rewritePrefix, upstream, wsClientOptions) { | ||
this.prefixList.push({ | ||
prefix: new URL(prefix, 'ws://127.0.0.1').pathname, | ||
rewritePrefix, | ||
upstream: convertUrlToWebSocket(upstream), | ||
wsClientOptions | ||
}) | ||
// sort by decreasing prefix length, so that findUpstreamUrl() does longest prefix match | ||
this.prefixList.sort((a, b) => b.prefix.length - a.prefix.length) | ||
} | ||
findUpstream (request) { | ||
const source = new URL(request.url, 'ws://127.0.0.1') | ||
for (const { prefix, rewritePrefix, upstream, wsClientOptions } of this.prefixList) { | ||
if (source.pathname.startsWith(prefix)) { | ||
const target = new URL(source.pathname.replace(prefix, rewritePrefix), upstream) | ||
target.search = source.search | ||
return { target, wsClientOptions } | ||
} | ||
} | ||
return undefined | ||
} | ||
handleConnection (source, request) { | ||
const upstream = this.findUpstream(request) | ||
if (!upstream) { | ||
this.logger.debug({ url: request.url }, 'not matching prefix') | ||
source.close() | ||
return | ||
} | ||
const { target: url, wsClientOptions } = upstream | ||
@@ -102,27 +141,28 @@ const subprotocols = [] | ||
const headers = { cookie: request.headers.cookie } | ||
optionsWs = { ...options.wsClientOptions, headers } | ||
optionsWs = { ...wsClientOptions, headers } | ||
} else { | ||
optionsWs = options.wsClientOptions | ||
optionsWs = wsClientOptions | ||
} | ||
const url = createWebSocketUrl(request) | ||
const target = new WebSocket(url, subprotocols, optionsWs) | ||
fastify.log.debug({ url: url.href }, 'proxy websocket') | ||
this.logger.debug({ url: url.href }, 'proxy websocket') | ||
proxyWebSockets(source, target) | ||
}) | ||
} | ||
} | ||
function createWebSocketUrl (request) { | ||
const source = new URL(request.url, 'ws://127.0.0.1') | ||
const httpWss = new WeakMap() // http.Server => WebSocketProxy | ||
const target = new URL( | ||
source.pathname.replace(fastify.prefix, rewritePrefix), | ||
convertUrlToWebSocket(options.upstream) | ||
) | ||
function setupWebSocketProxy (fastify, options, rewritePrefix) { | ||
let wsProxy = httpWss.get(fastify.server) | ||
if (!wsProxy) { | ||
wsProxy = new WebSocketProxy(fastify, options.wsServerOptions) | ||
httpWss.set(fastify.server, wsProxy) | ||
target.search = source.search | ||
fastify.addHook('onClose', (instance, done) => { | ||
httpWss.delete(fastify.server) | ||
wsProxy.close(done) | ||
}) | ||
} | ||
return target | ||
} | ||
wsProxy.addUpstream(fastify.prefix, rewritePrefix, options.upstream, options.wsClientOptions) | ||
} | ||
@@ -129,0 +169,0 @@ |
{ | ||
"name": "@fastify/http-proxy", | ||
"version": "8.1.0", | ||
"version": "8.2.0", | ||
"description": "proxy http requests, for Fastify", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -163,2 +163,6 @@ # @fastify/http-proxy | ||
[`@fastify/websocket`](https://github.com/fastify/fastify-websocket). | ||
Multiple websocket proxies may be attached to the same HTTP server at different paths. | ||
In this case, only the first `wsServerOptions` is applied. | ||
A few things are missing: | ||
@@ -165,0 +169,0 @@ |
@@ -60,2 +60,48 @@ 'use strict' | ||
test('multiple websocket upstreams', async (t) => { | ||
t.plan(8) | ||
const server = Fastify() | ||
for (const name of ['/A', '/A/B', '/C/D', '/C']) { | ||
const origin = createServer() | ||
const wss = new WebSocket.Server({ server: origin }) | ||
t.teardown(wss.close.bind(wss)) | ||
t.teardown(origin.close.bind(origin)) | ||
wss.once('connection', (ws) => { | ||
ws.once('message', message => { | ||
t.equal(message.toString(), `hello ${name}`) | ||
// echo | ||
ws.send(message) | ||
}) | ||
}) | ||
await promisify(origin.listen.bind(origin))({ port: 0 }) | ||
server.register(proxy, { | ||
prefix: name, | ||
upstream: `ws://localhost:${origin.address().port}`, | ||
websocket: true | ||
}) | ||
} | ||
await server.listen({ port: 0 }) | ||
t.teardown(server.close.bind(server)) | ||
const wsClients = [] | ||
for (const name of ['/A', '/A/B', '/C/D', '/C']) { | ||
const ws = new WebSocket(`ws://localhost:${server.server.address().port}${name}`) | ||
await once(ws, 'open') | ||
ws.send(`hello ${name}`) | ||
const [reply] = await once(ws, 'message') | ||
t.equal(reply.toString(), `hello ${name}`) | ||
wsClients.push(ws) | ||
} | ||
await Promise.all([ | ||
...wsClients.map(ws => once(ws, 'close')), | ||
server.close() | ||
]) | ||
}) | ||
test('captures errors on start', async (t) => { | ||
@@ -62,0 +108,0 @@ const app = Fastify() |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
54702
1425
197