Skip to content

Commit 5868e65

Browse files
authored
expose fastify server in websocket handlers (#67)
* expose fastify server in websocket handlers * test exposed server type in handlers
1 parent 227da9f commit 5868e65

File tree

5 files changed

+108
-19
lines changed

5 files changed

+108
-19
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,33 @@ fastify.listen(3000, err => {
119119
})
120120
```
121121

122+
_**NB:** Websocket handlers don't follow the usual `fastify` request lifecycle, they are handled by an independent router. You can still access the fastify server's decorations via `this` in both global and per route handlers_
123+
124+
```js
125+
'use strict'
126+
127+
const fastify = require('fastify')()
128+
129+
fastify.register(require('fastify-websocket'))
130+
131+
fastify.get('/', { websocket: true }, function wsHandler (connection, req) {
132+
// bound to fastify server
133+
this.myDecoration.someFunc()
134+
135+
connection.socket.on('message', message => {
136+
// message === 'hi from client'
137+
connection.socket.send('hi from server')
138+
})
139+
})
140+
141+
fastify.listen(3000, err => {
142+
if (err) {
143+
fastify.log.error(err)
144+
process.exit(1)
145+
}
146+
})
147+
```
148+
122149
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.
123150

124151
```js

index.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="node" />
22
import { IncomingMessage, ServerResponse, Server } from 'http';
3-
import { FastifyPlugin, FastifyRequest, RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, RequestGenericInterface, ContextConfigDefault } from 'fastify';
3+
import { FastifyPlugin, FastifyRequest, RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, RequestGenericInterface, ContextConfigDefault, FastifyInstance } from 'fastify';
44
import * as WebSocket from 'ws';
55
import { Duplex } from 'stream';
66

@@ -41,7 +41,7 @@ export interface SocketStream extends Duplex {
4141
}
4242

4343
export interface WebsocketPluginOptions {
44-
handle?: (connection: SocketStream) => void;
44+
handle?: (this: FastifyInstance, connection: SocketStream) => void;
4545
options?: WebSocket.ServerOptions;
4646
}
4747

index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function fastifyWebsocket (fastify, opts, next) {
1212
return next(new Error('invalid handle function'))
1313
}
1414
const handle = opts.handle
15-
? (req, res) => opts.handle(req[kWs], req)
15+
? (req, res) => opts.handle.call(fastify, req[kWs], req)
1616
: (req, res) => {
1717
req[kWs].socket.close()
1818
}
@@ -53,7 +53,7 @@ function fastifyWebsocket (fastify, opts, next) {
5353
}
5454

5555
router.on('GET', routeOptions.path, (req, _, params) => {
56-
const result = wsHandler(req[kWs], req, params)
56+
const result = wsHandler.call(fastify, req[kWs], req, params)
5757

5858
if (result && typeof result.catch === 'function') {
5959
result.catch(err => req[kWs].destroy(err))

test/router.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,3 +371,65 @@ test('Should not thow error when register empgy get with prefix', t => {
371371
})
372372
})
373373
})
374+
375+
test('Should expose fastify instance to websocket global handler', t => {
376+
const fastify = Fastify()
377+
378+
t.tearDown(() => fastify.close())
379+
380+
fastify.register(fastifyWebsocket, {
381+
handle: function wsHandler (conn, req) {
382+
t.equal(this, fastify, 'this is bound to fastify server')
383+
conn.write('empty')
384+
conn.end()
385+
},
386+
options: { path: '/ws' }
387+
})
388+
389+
fastify.listen(0, err => {
390+
t.error(err)
391+
const ws = new WebSocket(
392+
'ws://localhost:' + (fastify.server.address()).port + '/ws'
393+
)
394+
const client = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' })
395+
t.tearDown(client.destroy.bind(client))
396+
397+
client.setEncoding('utf8')
398+
399+
client.once('data', chunk => {
400+
t.equal(chunk, 'empty')
401+
client.end()
402+
t.end()
403+
})
404+
})
405+
})
406+
407+
test('Should expose fastify instance to websocket per-route handler', t => {
408+
const fastify = Fastify()
409+
410+
t.tearDown(() => fastify.close())
411+
412+
fastify.register(fastifyWebsocket)
413+
fastify.get('/ws', { websocket: true }, function wsHandler (conn, req) {
414+
t.equal(this, fastify, 'this is bound to fastify server')
415+
conn.write('empty')
416+
conn.end()
417+
})
418+
419+
fastify.listen(0, err => {
420+
t.error(err)
421+
const ws = new WebSocket(
422+
'ws://localhost:' + (fastify.server.address()).port + '/ws'
423+
)
424+
const client = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' })
425+
t.tearDown(client.destroy.bind(client))
426+
427+
client.setEncoding('utf8')
428+
429+
client.once('data', chunk => {
430+
t.equal(chunk, 'empty')
431+
client.end()
432+
t.end()
433+
})
434+
})
435+
})

test/types/index.test-d.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,25 @@ import { expectType } from 'tsd';
44
import { Server as HttpServer, IncomingMessage } from 'http'
55
import { Server } from 'ws';
66

7-
const handler: WebsocketHandler = (
7+
const app: FastifyInstance = fastify();
8+
app.register(wsPlugin);
9+
app.register(wsPlugin, {});
10+
app.register(wsPlugin, {
11+
handle: function globalHandler(connection: SocketStream): void {
12+
expectType<FastifyInstance>(this);
13+
expectType<SocketStream>(connection)
14+
}
15+
});
16+
app.register(wsPlugin, { options: { perMessageDeflate: true } });
17+
18+
app.get('/', { websocket: true }, function perRouteHandler(
819
connection: SocketStream,
920
req: IncomingMessage,
1021
params
11-
) => {
22+
) {
23+
expectType<FastifyInstance>(this);
1224
expectType<SocketStream>(connection);
1325
expectType<Server>(app.websocketServer);
1426
expectType<IncomingMessage>(req)
1527
expectType<{ [key: string]: any } | undefined>(params);
16-
};
17-
18-
const handle = (connection: SocketStream): void => {
19-
expectType<SocketStream>(connection)
20-
}
21-
22-
const app: FastifyInstance = fastify();
23-
app.register(wsPlugin);
24-
app.register(wsPlugin, {});
25-
app.register(wsPlugin, { handle } );
26-
app.register(wsPlugin, { options: { perMessageDeflate: true } });
27-
28-
app.get('/', { websocket: true }, handler);
28+
});

0 commit comments

Comments
 (0)