Skip to content

feat: The target websocket watch the specific url instead of upstream… #304

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,14 @@ A few things are missing:

Pull requests are welcome to finish this feature.

### `wsUpstream`
Working only if property `websocket` is `true`.

An URL (including protocol) that represents the target websockets to use for proxying websockets.
Accepted both `https://` and `wss://`.

Note that if property `wsUpstream` not specified then proxy will try to connect with the `upstream` property.

### `wsServerOptions`

The options passed to [`new ws.Server()`](https://github.com/websockets/ws/blob/HEAD/doc/ws.md#class-websocketserver).
Expand Down
24 changes: 21 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,19 @@ class WebSocketProxy {

findUpstream (request) {
const source = new URL(request.url, 'ws://127.0.0.1')

for (const { prefix, rewritePrefix, upstream, wsClientOptions } of this.prefixList) {
// If the "upstream" have path then need get the Base of url, otherwise the "target" path will be broken.
// Example: upstream is "ws://localhost:22/some/path" and after this code
// "target = new URL(source.pathname.replace(prefix, rewritePrefix), upstream)"
// The target.pathname will be "some/some/path"
const upstreamUrl = new URL(upstream)
const upstreamBase = upstreamUrl.pathname && upstreamUrl.pathname !== '/'
? upstreamUrl.href.replace(upstreamUrl.pathname, '')
: upstream

if (source.pathname.startsWith(prefix)) {
const target = new URL(source.pathname.replace(prefix, rewritePrefix), upstream)
const target = new URL(source.pathname.replace(prefix, rewritePrefix), upstreamBase)
target.search = source.search
return { target, wsClientOptions }
}
Expand Down Expand Up @@ -195,8 +205,16 @@ function setupWebSocketProxy (fastify, options, rewritePrefix) {
httpWss.set(fastify.server, wsProxy)
}

if (options.upstream !== '') {
wsProxy.addUpstream(fastify.prefix, rewritePrefix, options.upstream, options.wsClientOptions)
if (
(typeof options.wsUpstream === 'string' && options.wsUpstream !== '') ||
(typeof options.upstream === 'string' && options.upstream !== '')
) {
wsProxy.addUpstream(
fastify.prefix,
rewritePrefix,
options.wsUpstream ? options.wsUpstream : options.upstream,
options.wsClientOptions
)
// The else block is validate earlier in the code
} else {
wsProxy.findUpstream = function (request) {
Expand Down
65 changes: 65 additions & 0 deletions test/websocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -407,3 +407,68 @@ test('Should gracefully close when clients attempt to connect after calling clos
await server.close()
await p
})

test('Proxy websocket with custom upstream url', async (t) => {
t.plan(5)

const origin = createServer()
const wss = new WebSocket.Server({ server: origin })

t.teardown(wss.close.bind(wss))
t.teardown(origin.close.bind(origin))

const serverMessages = []
wss.on('connection', (ws, request) => {
ws.on('message', (message, binary) => {
// Also need save request.url for check from what url the message is coming.
serverMessages.push([message.toString(), binary, request.url])
ws.send(message, { binary })
})
})

await promisify(origin.listen.bind(origin))({ port: 0 })
// Path for wsUpstream and for later check.
const path = '/some/path'
const server = Fastify()
server.register(proxy, {
upstream: `ws://localhost:${origin.address().port}`,
// Start proxy with different upstream, added path.
wsUpstream: `ws://localhost:${origin.address().port}${path}`,
websocket: true
})

await server.listen({ port: 0 })
t.teardown(server.close.bind(server))

// Start websocket with different upstream for connect, added path.
const ws = new WebSocket(`ws://localhost:${server.server.address().port}${path}`)
await once(ws, 'open')

const data = [{ message: 'hello', binary: false }, { message: 'fastify', binary: true, isBuffer: true }]
const dataLength = data.length
let dataIndex = 0

for (; dataIndex < dataLength; dataIndex++) {
const { message: msg, binary, isBuffer } = data[dataIndex]
const message = isBuffer
? Buffer.from(msg)
: msg

ws.send(message, { binary })

const [reply, binaryAnswer] = await once(ws, 'message')

t.equal(reply.toString(), msg)
t.equal(binaryAnswer, binary)
}
// Also check "path", must be the same.
t.strictSame(serverMessages, [
['hello', false, path],
['fastify', true, path]
])

await Promise.all([
once(ws, 'close'),
server.close()
])
})
15 changes: 13 additions & 2 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,19 @@ import {

import { ClientOptions, ServerOptions } from 'ws';

type FastifyHttpProxy = FastifyPluginCallback<fastifyHttpProxy.FastifyHttpProxyOptions>;
interface FastifyHttpProxyWebsocketOptionsEnabled {
websocket: true;
wsUpstream?: string;
}
interface FastifyHttpProxyWebsocketOptionsDisabled {
websocket?: false | never;
wsUpstream?: never;
}

type FastifyHttpProxy = FastifyPluginCallback<
fastifyHttpProxy.FastifyHttpProxyOptions
& (FastifyHttpProxyWebsocketOptionsEnabled | FastifyHttpProxyWebsocketOptionsDisabled)
>;

declare namespace fastifyHttpProxy {
export interface FastifyHttpProxyOptions extends FastifyReplyFromOptions {
Expand All @@ -21,7 +33,6 @@ declare namespace fastifyHttpProxy {
beforeHandler?: preHandlerHookHandler;
config?: Object;
replyOptions?: FastifyReplyFromHooks;
websocket?: boolean;
wsClientOptions?: ClientOptions;
wsServerOptions?: ServerOptions;
httpMethods?: string[];
Expand Down
19 changes: 18 additions & 1 deletion types/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,28 @@ app.register(fastifyHttpProxy, {
timeout: 20000
}
},
constraints: { version: '1.0.2' }
constraints: { version: '1.0.2' },
websocket: true,
wsUpstream: 'ws://origin.asd/connection'
});

expectError(
app.register(fastifyHttpProxy, {
thisOptionDoesNotExist: 'triggers a typescript error'
})
);

expectError(
app.register(fastifyHttpProxy, {
upstream: 'http://origin.asd',
wsUpstream: 'ws://origin.asd'
})
);

expectError(
app.register(fastifyHttpProxy, {
upstream: 'http://origin.asd',
websocket: false,
wsUpstream: 'asdf'
})
);