diff --git a/README.md b/README.md index d98f58d..f4eb3fe 100644 --- a/README.md +++ b/README.md @@ -227,7 +227,7 @@ It also supports an additional `rewriteRequestHeaders(headers, request)` functio opening the WebSocket connection. This function should return an object with the given headers. The default implementation forwards the `cookie` header. -## `wsReconnect` +### `wsReconnect` **Experimental.** (default: `disabled`) @@ -247,7 +247,7 @@ To enable the feature, set the `wsReconnect` option to an object with the follow See the example in [examples/reconnection](examples/reconnection). -## wsHooks +### `wsHooks` On websocket events, the following hooks are available, note **the hooks are all synchronous**. The `context` object is passed to all hooks and contains the `log` property. @@ -259,6 +259,26 @@ The `context` object is passed to all hooks and contains the `log` property. - `onReconnect`: A hook function that is called when the connection is reconnected `onReconnect(context, source, target)` (default: `undefined`). The function is called if reconnection feature is enabled. - `onPong`: A hook function that is called when the target responds to the ping `onPong(context, source, target)` (default: `undefined`). The function is called if reconnection feature is enabled. +## Decorators + +### `reply.fromParameters(url[, params[, prefix]])` + +It can be used to get the final URL and options that `@fastify/http-proxy` would have used to invoke `reply.from`. + +A typical use is to override the request URL: + +```javascript +preHandler (request, reply, done) { + if (request.url !== '/original') { + done() + return + } + + const { url, options } = reply.fromParameters('/updated', { ...request.params, serverId: 42 }) + reply.from(url, options) +} +``` + ## Benchmarks The following benchmarks were generated on a dedicated server with an Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz and 64GB of RAM: diff --git a/index.js b/index.js index 7ac0ccc..f8f7c4f 100644 --- a/index.js +++ b/index.js @@ -592,16 +592,16 @@ async function fastifyHttpProxy (fastify, opts) { return components } - function handler (request, reply) { - const { path, queryParams } = extractUrlComponents(request.url) + function fromParameters (url, params = {}, prefix = '/') { + const { path, queryParams } = extractUrlComponents(url) let dest = path - if (this.prefix.includes(':')) { + if (prefix.includes(':')) { const requestedPathElements = path.split('/') - const prefixPathWithVariables = this.prefix.split('/').map((_, index) => requestedPathElements[index]).join('/') + const prefixPathWithVariables = prefix.split('/').map((_, index) => requestedPathElements[index]).join('/') let rewritePrefixWithVariables = rewritePrefix - for (const [name, value] of Object.entries(request.params)) { + for (const [name, value] of Object.entries(params)) { rewritePrefixWithVariables = rewritePrefixWithVariables.replace(`:${name}`, value) } @@ -610,20 +610,34 @@ async function fastifyHttpProxy (fastify, opts) { dest += `?${qs.stringify(queryParams)}` } } else { - dest = dest.replace(this.prefix, rewritePrefix) + dest = dest.replace(prefix, rewritePrefix) } + return { url: dest || '/', options: replyOpts } + } + + function handler (request, reply) { + const { url, options } = fromParameters(request.url, request.params, this.prefix) + if (request.raw[kWs]) { reply.hijack() try { - wsProxy.handleUpgrade(request, dest || '/', noop) + wsProxy.handleUpgrade(request, url, noop) } /* c8 ignore start */ catch (err) { request.log.warn({ err }, 'websocket proxy error') } /* c8 ignore stop */ return } - reply.from(dest || '/', replyOpts) + reply.from(url, options) } + + fastify.decorateReply('fromParameters', fromParameters) + fastify.decorateReply('wsProxy', { + /* c8 ignore next 3 */ + getter () { + return wsProxy + } + }) } module.exports = fp(fastifyHttpProxy, { diff --git a/test/test.js b/test/test.js index c519ff5..2b8880a 100644 --- a/test/test.js +++ b/test/test.js @@ -921,6 +921,43 @@ async function run () { const queryParams = JSON.stringify(qs.parse('foo=bar&foo=baz&abc=qux')) t.assert.strictEqual(firstProxyPrefix.body, `this is "variable-api" endpoint with id 123 and query params ${queryParams}`) }) + + test('manual from call via fromParameters', async t => { + const server = Fastify() + server.register(proxy, { + upstream: `http://localhost:${origin.server.address().port}`, + preHandler (request, reply, done) { + if (request.url !== '/fake-a') { + done() + return + } + + const { url, options } = reply.fromParameters('/a') + reply.from(url, options) + } + }) + + await server.listen({ port: 0 }) + t.after(() => server.close()) + + { + const { + statusCode, + body + } = await got(`http://localhost:${server.server.address().port}/`) + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(body, 'this is root') + } + + { + const { + statusCode, + body + } = await got(`http://localhost:${server.server.address().port}/fake-a`) + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(body, 'this is a') + } + }) } run()