Skip to content

Commit 6f776a0

Browse files
fox1tEomm
authored andcommitted
Adds support for routing (#26)
1 parent 4986cd7 commit 6f776a0

File tree

6 files changed

+463
-26
lines changed

6 files changed

+463
-26
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,4 @@ typings/
5858
.env
5959

6060
package-lock.json
61+
.vscode

README.md

Lines changed: 116 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# fastify-websocket
2-
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) [![Build Status](https://travis-ci.org/fastify/fastify-websocket.svg?branch=master)](https://travis-ci.org/fastify/fastify-websocket) [![Greenkeeper badge](https://badges.greenkeeper.io/fastify/fastify-websocket.svg)](https://greenkeeper.io/)
2+
3+
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) [![Build Status](https://travis-ci.org/fastify/fastify-websocket.svg?branch=master)](https://travis-ci.org/fastify/fastify-websocket) [![Greenkeeper badge](https://badges.greenkeeper.io/fastify/fastify-websocket.svg)](https://greenkeeper.io/)
34

45
WebSocket support for [Fastify](https://github.com/fastify/fastify).
56
Built upon [websocket-stream](http://npm.im/websocket-stream).
@@ -12,45 +13,146 @@ npm install fastify-websocket --save
1213

1314
## Usage
1415

15-
All you need to do is to add it to your project with `register` and you are done!
16+
There are two possible ways of using this plugin: with a **global handler** or with a **per route handler**.
17+
18+
### Global handler
1619

17-
### Example
20+
All you need to do is to add it to your project with `register` and pass handle function. You are done!
1821

1922
```js
2023
'use strict'
2124

2225
const fastify = require('fastify')()
2326

24-
const wssOptions = {
25-
maxPayload: 1048576, // we set the maximum allowed messages size to 1 MiB (1024 bytes * 1024 bytes)
26-
path: '/fastify', // we accept only connections matching this path e.g.: ws://localhost:3000/fastify
27-
verifyClient: function (info, next) {
28-
if (info.req.headers['x-fastify-header'] !== 'fastify is awesome !') {
29-
return next(false) // the connection is not allowed
27+
fastify.register(require('fastify-websocket'), {
28+
handle,
29+
options: {
30+
maxPayload: 1048576, // we set the maximum allowed messages size to 1 MiB (1024 bytes * 1024 bytes)
31+
path: '/fastify', // we accept only connections matching this path e.g.: ws://localhost:3000/fastify
32+
verifyClient: function (info, next) {
33+
if (info.req.headers['x-fastify-header'] !== 'fastify is awesome !') {
34+
return next(false) // the connection is not allowed
35+
}
36+
next(true) // the connection is allowed
3037
}
38+
}
39+
})
40+
41+
function handle (conn) {
42+
conn.pipe(conn) // creates an echo server
43+
}
44+
45+
fastify.listen(3000, err => {
46+
if (err) {
47+
fastify.log.error(err)
48+
process.exit(1)
49+
}
50+
})
51+
```
3152

32-
next(true) // the connection is allowed
53+
### Per route handler
54+
55+
After registring this plugin, you can choose on which routes the WS server will respond. This could be achieved adding `websocket: true` property to `routeOptions` on a fastify's `.get` route. In this case two arguments will be passed to the handler: socket connection and the original `http.IncomingMessage` (instead of the usual fastify's request and reply objects).
56+
57+
```js
58+
'use strict'
59+
60+
const fastify = Fastify()
61+
62+
fastify.register(require('fastify-websocket'))
63+
64+
fastify.get('/', { websocket: true }, (connection, req) => {
65+
connection.socket.on('message', message => {
66+
// message === 'hi from client'
67+
connection.socket.send('hi from server')
68+
})
69+
})
70+
71+
fastify.listen(3000, err => {
72+
if (err) {
73+
fastify.log.error(err)
74+
process.exit(1)
3375
}
76+
})
77+
```
78+
79+
In this case there won't be any global handler, so it will respond with a 404 error on every unregistered route, closing the incoming upgrade connection requests.
80+
81+
However you can still pass a default global handler, that will be used as default handler.
82+
83+
```js
84+
'use strict'
85+
86+
const fastify = require('fastify')()
87+
88+
function handle (conn) {
89+
conn.pipe(conn) // creates an echo server
3490
}
3591

3692
fastify.register(require('fastify-websocket'), {
3793
handle,
38-
options: wssOptions
94+
options: { maxPayload: 1048576 }
3995
})
4096

97+
fastify.get('/', { websocket: true }, (connection, req) => {
98+
connection.socket.on('message', message => {
99+
// message === 'hi from client'
100+
connection.socket.send('hi from server')
101+
})
102+
})
103+
104+
fastify.listen(3000, err => {
105+
if (err) {
106+
fastify.log.error(err)
107+
process.exit(1)
108+
}
109+
})
110+
```
111+
112+
If you need to handle both HTTP requests and incoming socket connections on the same route, you can still do it using the [full declaration syntax](https://www.fastify.io/docs/latest/Routes/#full-declaration), adding a `wsHandler` property.
113+
114+
```js
115+
'use strict'
116+
117+
const fastify = require('fastify')()
118+
41119
function handle (conn) {
42120
conn.pipe(conn) // creates an echo server
43121
}
44122

45-
fastify.listen(3000, (err) => {
123+
fastify.register(require('fastify-websocket'), {
124+
handle,
125+
options: { maxPayload: 1048576 }
126+
})
127+
128+
fastify.route({
129+
method: 'GET',
130+
url: '/hello',
131+
handler: (req, reply) => {
132+
// this will handle http requests
133+
reply.send({ hello: 'world' })
134+
},
135+
wsHandler: (conn, req) => {
136+
// this will handle websockets connections
137+
conn.setEncoding('utf8')
138+
conn.write('hello client')
139+
140+
conn.once('data', chunk => {
141+
conn.end()
142+
})
143+
}
144+
})
145+
146+
fastify.listen(3000, err => {
46147
if (err) {
47-
fastify.log.error(err);
48-
process.exit(1);
148+
fastify.log.error(err)
149+
process.exit(1)
49150
}
50151
})
51152
```
52153

53154
## Options :
155+
54156
`fastify-websocket` accept the same options as [`websocket-stream`](https://github.com/maxogden/websocket-stream#options) and as [`ws`](https://github.com/websockets/ws/blob/master/doc/ws.md#new-websocketserveroptions-callback) :
55157

56158
- `objectMode` - Send each chunk on its own, and do not try to pack them in a single websocket frame.
@@ -70,10 +172,6 @@ For more informations you can check [`ws` options documentation](https://github.
70172

71173
_**NB:** By default if you do not provide a `server` option `fastify-websocket` will bind your websocket server instance to the scoped `fastify` instance._
72174

73-
## TODO
74-
75-
* [ ] Support Hooks?
76-
77175
## Acknowledgements
78176

79177
This project is kindly sponsored by [nearForm](http://nearform.com).

index.js

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

3+
const { ServerResponse } = require('http')
34
const fp = require('fastify-plugin')
45
const websocket = require('websocket-stream')
6+
const findMyWay = require('find-my-way')
7+
8+
const kWs = Symbol('ws')
59

610
function fastifyWebsocket (fastify, opts, next) {
11+
if (opts.handle && typeof opts.handle !== 'function') {
12+
return next(new Error('invalid handle function'))
13+
}
714
const handle = opts.handle
15+
? (req, res) => opts.handle(req[kWs], req)
16+
: (req, res) => { req[kWs].socket.close() }
17+
818
const options = Object.assign({ server: fastify.server }, opts.options)
919

10-
if (typeof handle !== 'function') {
11-
return next(new Error('invalid handle function'))
12-
}
20+
const router = findMyWay({
21+
ignoreTrailingSlash: true,
22+
defaultRoute: handle
23+
})
1324

14-
const wss = websocket.createServer(options, handle)
25+
const wss = websocket.createServer(options, handleRouting)
1526

1627
fastify.decorate('websocketServer', wss)
1728

29+
fastify.addHook('onRoute', routeOptions => {
30+
if (routeOptions.websocket || routeOptions.wsHandler) {
31+
if (routeOptions.method !== 'GET') {
32+
throw new Error('websocket handler can only be declared in GET method')
33+
}
34+
35+
let wsHandler = routeOptions.wsHandler
36+
let handler = routeOptions.handler
37+
38+
if (routeOptions.websocket) {
39+
wsHandler = routeOptions.handler
40+
handler = function (request, reply) {
41+
reply.code(404).send()
42+
}
43+
}
44+
45+
if (typeof wsHandler !== 'function') {
46+
throw new Error('invalid wsHandler function')
47+
}
48+
49+
router.on('GET', routeOptions.path, (req, _) => {
50+
const result = wsHandler(req[kWs], req)
51+
52+
if (result && typeof result.catch === 'function') {
53+
result.catch((err) => req[kWs].destroy(err))
54+
}
55+
})
56+
57+
routeOptions.handler = handler
58+
}
59+
})
60+
1861
fastify.addHook('onClose', close)
1962

63+
function handleRouting (connection, request) {
64+
const response = new ServerResponse(request)
65+
request[kWs] = connection
66+
router.lookup(request, response)
67+
}
68+
2069
next()
2170
}
2271

@@ -25,6 +74,6 @@ function close (fastify, done) {
2574
}
2675

2776
module.exports = fp(fastifyWebsocket, {
28-
fastify: '>=0.39.0',
77+
fastify: '>=2.4.1',
2978
name: 'fastify-websocket'
3079
})

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "basic websocket support for fastify",
55
"main": "index.js",
66
"scripts": {
7-
"test": "standard | snazzy && tap test.js"
7+
"test": "standard | snazzy && tap \"test/*.js\""
88
},
99
"repository": {
1010
"type": "git",
@@ -21,14 +21,15 @@
2121
},
2222
"homepage": "https://github.com/fastify/fastify-websocket#readme",
2323
"devDependencies": {
24-
"fastify": "^2.3.0",
24+
"fastify": "^2.4.1",
2525
"pre-commit": "^1.2.2",
2626
"snazzy": "^8.0.0",
2727
"standard": "^12.0.1",
2828
"tap": "^12.7.0"
2929
},
3030
"dependencies": {
3131
"fastify-plugin": "^1.5.0",
32+
"find-my-way": "^2.0.1",
3233
"websocket-stream": "^5.5.0"
3334
},
3435
"greenkeeper": {

test.js renamed to test/base.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
const http = require('http')
44
const test = require('tap').test
55
const Fastify = require('fastify')
6-
const fastifyWebsocket = require('.')
6+
const fastifyWebsocket = require('../')
77
const websocket = require('websocket-stream')
88

99
test('Should expose a websocket', (t) => {

0 commit comments

Comments
 (0)