Skip to content

Commit 608ebb5

Browse files
mcollinajsumners
andauthored
Added basic websocket support (#89)
* Added basic websocket support * Websocket support * Update index.js Co-authored-by: James Sumners <[email protected]> * Fix old nodes Co-authored-by: James Sumners <[email protected]>
1 parent 0483360 commit 608ebb5

File tree

4 files changed

+122
-5
lines changed

4 files changed

+122
-5
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,20 @@ configuration passed to the route.
118118

119119
Object with [reply options](https://github.com/fastify/fastify-reply-from#replyfromsource-opts) for `fastify-reply-from`.
120120

121+
## websocket
122+
123+
This module has _partial_ support for forwarding websockets by passing a
124+
`websocket` option. All those options are going to be forwarded to
125+
[`fastify-websocket`](https://github.com/fastify/fastify-websocket).
126+
A few things are missing:
127+
128+
1. forwarding headers as well as `rewriteHeaders`
129+
2. support for paths, `prefix` and `rewritePrefix`
130+
3. request id logging
131+
132+
Pull requests are welcome to finish this feature.
133+
134+
121135
## Benchmarks
122136

123137
The following benchmarks where generated on a Macbook 2018 with i5 and
@@ -137,6 +151,7 @@ URL`.
137151

138152
* [ ] Generate unique request ids and implement request tracking
139153
* [ ] Perform validations for incoming data
154+
* [ ] Finish implementing websocket (follow TODO)
140155

141156
## License
142157

index.js

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
'use strict'
22

33
const From = require('fastify-reply-from')
4+
const WebSocketPlugin = require('fastify-websocket')
5+
const WebSocket = require('ws')
6+
const { pipeline } = require('stream')
7+
const nonWsMethods = ['DELETE', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS']
48

59
module.exports = async function (fastify, opts) {
610
if (!opts.upstream) {
@@ -42,12 +46,59 @@ module.exports = async function (fastify, opts) {
4246
done(null, req)
4347
}
4448

45-
fastify.all('/', { preHandler, config: opts.config || {} }, reply)
46-
fastify.all('/*', { preHandler, config: opts.config || {} }, reply)
49+
if (opts.websocket) {
50+
fastify.register(WebSocketPlugin, opts.websocket)
51+
}
52+
53+
fastify.get('/', {
54+
preHandler,
55+
config: opts.config || {},
56+
handler,
57+
wsHandler
58+
})
59+
fastify.get('/*', {
60+
preHandler,
61+
config: opts.config || {},
62+
handler,
63+
wsHandler
64+
})
65+
66+
fastify.route({
67+
url: '/',
68+
method: nonWsMethods,
69+
preHandler,
70+
config: opts.config || {},
71+
handler
72+
})
73+
fastify.route({
74+
url: '/*',
75+
method: nonWsMethods,
76+
preHandler,
77+
config: opts.config || {},
78+
handler
79+
})
4780

48-
function reply (request, reply) {
81+
function handler (request, reply) {
4982
var dest = request.req.url
5083
dest = dest.replace(this.prefix, rewritePrefix)
5184
reply.from(dest || '/', replyOpts)
5285
}
86+
87+
function wsHandler (conn, req) {
88+
// TODO support paths and querystrings
89+
// TODO support rewriteHeader
90+
// TODO support rewritePrefix
91+
const ws = new WebSocket(opts.upstream)
92+
const stream = WebSocket.createWebSocketStream(ws)
93+
94+
// TODO fastify-websocket should create a logger for each connection
95+
fastify.log.info('starting websocket tunnel')
96+
pipeline(conn, stream, conn, function (err) {
97+
if (err) {
98+
fastify.log.info({ err }, 'websocket tunnel terminated with error')
99+
return
100+
}
101+
fastify.log.info('websocket tunnel terminated')
102+
})
103+
}
53104
}

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "proxy http requests, for Fastify",
55
"main": "index.js",
66
"scripts": {
7-
"test": "standard | snazzy && tap test/test.js",
7+
"test": "standard | snazzy && tap test/*.js",
88
"lint:typescript": "standard --fix --parser @typescript-eslint/parser --plugin typescript test/types/*.ts",
99
"typescript": "tsc --project ./test/types/tsconfig.json"
1010
},
@@ -42,6 +42,8 @@
4242
"typescript": "^3.4.5"
4343
},
4444
"dependencies": {
45-
"fastify-reply-from": "^2.0.0"
45+
"fastify-reply-from": "^2.0.0",
46+
"fastify-websocket": "^1.1.2",
47+
"ws": "^7.2.5"
4648
}
4749
}

test/websocket.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use strict'
2+
3+
const { test } = require('tap')
4+
const Fastify = require('fastify')
5+
const proxy = require('../')
6+
const WebSocket = require('ws')
7+
const { createServer } = require('http')
8+
const { promisify } = require('util')
9+
const { once } = require('events')
10+
11+
test('basic websocket proxy', async (t) => {
12+
t.plan(2)
13+
14+
const origin = createServer()
15+
const wss = new WebSocket.Server({ server: origin })
16+
t.tearDown(wss.close.bind(wss))
17+
t.tearDown(origin.close.bind(origin))
18+
19+
wss.on('connection', (ws) => {
20+
ws.on('message', (message) => {
21+
t.equal(message.toString(), 'hello')
22+
// echo
23+
ws.send(message)
24+
})
25+
})
26+
27+
await promisify(origin.listen.bind(origin))(0)
28+
29+
const server = Fastify()
30+
server.register(proxy, {
31+
upstream: `http://localhost:${origin.address().port}`,
32+
websocket: true
33+
})
34+
35+
await server.listen(0)
36+
t.tearDown(server.close.bind(server))
37+
38+
const ws = new WebSocket(`http://localhost:${server.server.address().port}`)
39+
40+
await once(ws, 'open')
41+
42+
const stream = WebSocket.createWebSocketStream(ws)
43+
44+
stream.write('hello')
45+
46+
const [buf] = await once(stream, 'data')
47+
48+
t.is(buf.toString(), 'hello')
49+
})

0 commit comments

Comments
 (0)