fastify / fastify-websocket Goto Github PK
View Code? Open in Web Editor NEWbasic websocket support for fastify
License: MIT License
basic websocket support for fastify
License: MIT License
When a client send more than a total of 16384 bytes to the server he is no longer able to send, but it can still receive data from the server. It's not necessary to send the 16384 bytes or more in a single message, this can be four times 5000 bytes for example. I also observed that the message that will exceed the 16384 bytes will be fully send, In the above example the fouth message of 5000 bytes will be entirely send but the next one won't.
To Reproduce, just execute the code below
/* /// SERVER /// */
'use strict'
const fastify = require('fastify')({logger: true})
fastify.register(require('fastify-websocket'), {
handle,
options: {
maxPayload: 1048576, // 1Mio
path: '/',
}
})
function handle (conn) {
conn.socket.on('message', (msg) => {
console.log(msg)
conn.socket.send(msg)
})
}
const start = async () => {
try {
await fastify.listen(3000, "0.0.0.0")
fastify.log.info(`server listening on ${fastify.server.address().port}`)
} catch (err) {
fastify.log.error(err)
process.exit(1)
}
}
start()
/* /// CLIENT /// */
var socket = new WebSocket("ws://<ip adress>:3000");
socket.addEventListener('message', (msg) => {
console.log(msg);
})
var data = "a".repeat(16384);
socket.send(data); // This one will be see in the server console and the client will receive the "echo"
socket.send(data); // This one won't be see in the server and client console
Fastify 3.10, together with the use of prefixes, breaks fastify-websocket
.
fastify.register(
(fastify, opts, done) => {
fastify.get("/", { websocket: true }, (connection, req) => {
connection.socket.send("This works");
});
done();
},
{ prefix: "/activity" }
);
index.js
ws://localhost:3000/activity
Prior to 3.10, this line was called twice per route:
Line 36 in 99c94a0
That's why this lines makes sure to ignore one of them:
Line 42 in 99c94a0
Now with 3.10, the onRoute
is called only once, and all of the routes are effectively ignored.
originally encountered in quirrel-dev/quirrel#49
Hi,
I tried below code to run socket.
const fastify = require('fastify')()
function handle (conn) {
conn.pipe(conn) // creates an echo server
}
fastify.register(require('fastify-websocket'), {
handle,
options: { maxPayload: 1048576 }
})
fastify.get('/', { websocket: true }, (connection, req) => {
connection.socket.on('message', message => {
// message === 'hi from client'
connection.socket.send('hi from server')
})
})
fastify.listen(3000, err => {
if (err) {
fastify.log.error(err)
process.exit(1)
}
})
And from my, I connected to my socket by using var test = new WebSocket('ws://localhost:3000')
. How I can trigger /
API with my socket connection. Please help me with some examples.
fastify-websocket
currently passes the raw http.IncomingMessage
into websocket route handlers, not a FastifyRequest
object. I think instead passing a FastifyRequest
to handlers would improve a number of things.
First off, it is unfortunate to not have access to FastifyRequest
's basic helpers. The higher level object is more useful than the raw IncomingMessage
and has some handy doodads for getting say route params or the logger to work with. Second, fastify promotes and has a really healthy ecosystem of things that decorate FastifyRequest
that websocket handlers also can't access. Things like a session from fastify-secure-session
, utilities from fastify-url-data
, or user-land decorators to get a database client or what have you aren't available in route handlers.
Finally, it's unfortunate that hooks don't run for websocket handlers. We'd have to discuss semantically how this might work, but, if we could come up with sane semantics for this, it'd be really handy. I want to use hooks to implement authorization for example -- deny requests based on the presence of a session value or whatever. This same logic needs to apply to work done over normal HTTP requests as well as work done over websocket connections, but currently I have to duplicate it in a hook and in a verifyClient
option. One place (especially one place that had access to decorators) would be simpler.
I also think there's an argument to be made that websocket handlers being different like this violates the principle of least surprise.
This'd definitely be a breaking change, so knowing that, we'd either introduce a new API or just do a major version bump. With that, I think it'd be possible to actually implement this by using ws
ability to bind to an existing http.Server
, as is detailed here: https://www.npmjs.com/package/ws#external-https-server
I think for an UPGRADE
request we'd probably not want to provide a FastifyReply
object, or we'd want to change the API to use FastifyReply
to actually start the websocket connection. I think there's really only one thing you want to do with an upgrade request, which is delegate it to the websocket server, so I think having to do that manually isn't really worth it, and that it's a special enough scenario that having a different handler signature is actually a good thing anyways. So I'd suggest having websocket handlers look pretty similar to how the do right now.
Hooks wise, the semantics that make sense to me are firing everything up until the reply is sending. So, onRequest
, preParsing
, preValidation
, preHandler
, preSerialization
, and onError
could all be relied on, but onSend
and onResponse
would never be called and would be documented as such. I am not really familiar with Fastify's internals enough to say how hard it would be to actually do this weird half-chain of hooks, but were that easy enough, this makes sense to me.
I don't really know why fastify-websocket
works the way it does right now, but if I had to guess, I'd say it's because its really simple to implement. That's not a bad reason. I am finding that I have to do more work in userland because of this though, and I think implementing this inside the library instead of in bespoke and crappy ways outside it each time would save devs lots of time.
Thoughts?
Hello,
I'm having an issue where I'm sending both json strings as well as flatbuffer/uin8 arrays to my websocket connection.
In on("message"
I'm calling toString
but that doesnt work for the Uint8Arrays being sent. How should I handle both message types / is there a flag to enable?
Current version: 4.2.2
error when calling toString
on a message that is actually an Uint8Array
SyntaxError: Unexpected token ΒΆ in JSON at position 0
at JSON.parse (<anonymous>)
at C:\git\needle-tiny-networking-package\packages\websocket\src\networking.js:52:28
at WebSocket.<anonymous> (C:\git\needle-tiny-networking-package\packages\websocket\src\proxy.js:31:17)
at WebSocket.emit (events.js:388:22)
at Receiver.receiverOnMessage (C:\git\needle-tiny-networking-package\packages\websocket\node_modules\fastify-websocket\node_modules\ws\lib\websocket.js:1137:20)
at Receiver.emit (events.js:376:20)
at Receiver.dataMessage (C:\git\needle-tiny-networking-package\packages\websocket\node_modules\fastify-websocket\node_modules\ws\lib\receiver.js:513:14)
at Receiver.getData (C:\git\needle-tiny-networking-package\packages\websocket\node_modules\fastify-websocket\node_modules\ws\lib\receiver.js:446:17)
at Receiver.startLoop (C:\git\needle-tiny-networking-package\packages\websocket\node_modules\fastify-websocket\node_modules\ws\lib\receiver.js:148:22)
at Receiver._write (C:\git\needle-tiny-networking-package\packages\websocket\node_modules\fastify-websocket\node_modules\ws\lib\receiver.js:83:10)
at writeOrBuffer (internal/streams/writable.js:358:12)
at Receiver.Writable.write (internal/streams/writable.js:303:10)
at Socket.socketOnData (C:\git\needle-tiny-networking-package\packages\websocket\node_modules\fastify-websocket\node_modules\ws\lib\websocket.js:1231:35)
at Socket.emit (events.js:376:20)
at addChunk (internal/streams/readable.js:309:12)
at readableAddChunk (internal/streams/readable.js:284:9)
3.15.1
14.5.0
Linux
20.04
The WebsocketRouteOptions
doesn't accept generics, so WebsocketHandler
is left with the defaults, not with the appropriate types.
import fastify from "fastify"
import fastifyWs from "fastify-websocket"
const fastify = fastify()
fastify.register(fastifyWs)
Let's say we have this interface declared:
interface AuthenticatedEndpoint extends RequestGenericInterface {
Body: {
auth: string
}
}
When using it with app.route
:
fastify.route<AuthenticatedEndpoint>({
method: 'GET',
url: '/ws',
wsHandler(conn, req) {
req.body // typed as `unknown`
req.body.auth // TypeError
}
})
(assuming we have the same starting code as we had in Steps to Reproduce)
fastify.route<AuthenticatedEndpoint>({
method: 'GET',
url: '/ws',
wsHandler(conn, req) {
req.body // typed as { auth: string }
req.body.auth // string
}
})
It seems not to be FastifyRequest
req.params
. (I now know it is in the third (3rd) params)req.log
is undefined, not Pino Logger. I have to import Pino (and install both pino
and @types/pino
) directly to activate logging.req
.FastifyRequest
if it does not have params
nor log
. "fastify": "^2.14.1",
"fastify-cors": "^3.0.3",
"fastify-file-upload": "^2.0.1",
"fastify-helmet": "^3.0.2",
"fastify-oas": "^2.7.0",
"fastify-static": "^2.7.0",
"fastify-websocket": "^1.1.2",
3.24.0
4.0.0
16
macOS
11.6.1
Hi,
I have tried to implement an asynchronous task in a wsHandler as described in the documentation. However, I get a TypeError as soon as I call request.getSession()
. Either I have misunderstood something or the method is no longer available in the current version?
const { QueryService } = require('@lib/infrastructure/database')
const enums = require('@lib/common/enums')
module.exports = function (fastify) {
return {
global: function (connection, request) {
const sessionPromise = request.getSession() // <-- TYPE_ERROR
connection.socket.on('close', async function (message) {
await sessionPromise()
const queryService = new QueryService()
const client = await fastify.pg.connect()
await client.query(queryService.players.updateState, [enums.playerStates.offline, request.query.playerId, request.query.gameId])
fastify.websocketServer.clients.forEach(function (client) {
if (client.readyState === 1) {
client.send(JSON.stringify({ ...request.query, type: 'close' }))
}
})
})
}
}
}
Reference: Repository
Use request.getSession()
No Error.
hello, my server http2, can i use this plugin for wss connection?
How to implement a websocket route using fastify-websocket in typescript?
Errors inside the handler are currently swallowed, which for me is unexpected behavior and inconsistent with the behavior for regular non-ws routes.
Steps to reproduce the behavior:
'use strict'
const fastify = require('fastify')({
logger: true
})
fastify.register(require('fastify-websocket'))
fastify.get('/ws', { websocket: true }, () => {
throw new Error('demonstration')
})
// Declare a route
fastify.get('/', (request, reply) => {
throw new Error('demonstration')
})
fastify.listen(3000, err => {
if (err) {
fastify.log.error(err)
process.exit(1)
}
})
wscat --connect ws://localhost:3000/ws
results in :
{
"level": 40,
"time": 1615569367599,
"pid": 28959,
"hostname": "alex-desktop",
"reqId": "req-1",
"err": {
"type": "FastifyError",
"message": "Reply was already sent.",
"stack": "FastifyError: Reply was already sent.\n at _Reply.Reply.send (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/reply.js:118:26)\n at preHandlerCallback (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/handleRequest.js:126:11)\n at preValidationCallback (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/handleRequest.js:107:5)\n at handler (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/handleRequest.js:70:7)\n at handleRequest (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/handleRequest.js:18:5)\n at runPreParsing (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/route.js:421:5)\n at Object.routeHandler [as handler] (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/route.js:379:7)\n at Router.lookup (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/find-my-way/index.js:339:14)\n at Server.<anonymous> (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify-websocket/index.js:50:15)\n at Server.emit (events.js:315:20)",
"name": "FastifyError",
"code": "FST_ERR_REP_ALREADY_SENT",
"statusCode": 500
},
"msg": "Reply already sent"
}
curl http://localhost:3000
:
{
"level": 50,
"time": 1615569798536,
"pid": 30071,
"hostname": "alex-desktop",
"reqId": "req-1",
"req": {
"method": "GET",
"url": "/",
"hostname": "localhost:3000",
"remoteAddress": "127.0.0.1",
"remotePort": 50970
},
"res": { "statusCode": 500 },
"err": {
"type": "Error",
"message": "demonstration",
"stack": "Error: demonstration\n at Object.<anonymous> (/home/alex/src/issues/issue-256-websocket-swallows-error/index.js:16:9)\n at Object.routeOptions.handler (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify-websocket/index.js:125:24)\n at preHandlerCallback (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/handleRequest.js:124:28)\n at preValidationCallback (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/handleRequest.js:107:5)\n at handler (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/handleRequest.js:70:7)\n at handleRequest (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/handleRequest.js:18:5)\n at runPreParsing (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/route.js:421:5)\n at Object.routeHandler [as handler] (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/route.js:379:7)\n at Router.lookup (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/find-my-way/index.js:339:14)\n at Server.emit (events.js:315:20)"
},
"msg": "demonstration"
}
For the ws handler the information about the original new Error('demonstration')
is completely lost.
Errors in my code are logged and I get a chance to track them down.
Dependabot encountered the following error when parsing your .dependabot/config.yml
:
The property '#/update_configs' of type object did not match the following type: array
Please update the config file to conform with Dependabot's specification using our docs and online validator.
In a TypeScript project, I am using the new 3.0.0 WebsocketHandler interface that provides FastifyRequest. I want to declare the request Header type so I can access them without casting or using //@ts-ignore as I do with HTTP request handlers.
Here is an example
server.get("/ws", {
websocket: true,
schema: {
headers: TenantHeaderSchema
}
},
// compiler error, see below
createWSHandler())
...
function createWSHandler() {
return (conn: SocketStream,
request: FastifyRequest<{Headers: TenantHTTPHeader}>) => {
const {tenant} = request.headers
...
Here is the compiler error:
Overload 1 of 4, '(path: string, opts: RouteShorthandOptions<Server, IncomingMessage, ServerResponse, RequestGenericInterface, unknown> & { ...; }, handler?: WebsocketHandler | undefined): FastifyInstance<...>', gave the following error.
Argument of type '(conn: SocketStream, request: FastifyRequest<{ Headers: TenantHTTPHeader;}>) => void' is not assignable to parameter of type 'WebsocketHandler'.
Types of parameters 'request' and 'request' are incompatible.
Type 'FastifyRequest<RouteGenericInterface, Server, IncomingMessage>' is not assignable to type 'FastifyRequest<{ Headers: TenantHTTPHeader; }, Server, IncomingMessage>'.
Type 'RouteGenericInterface' is not assignable to type '{ Headers: TenantHTTPHeader; }'.
Types of property 'Headers' are incompatible.
Type 'unknown' is not assignable to type 'TenantHTTPHeader'.
I expect the FastifyRequest declaration to accept Headers declarations as they do in other get() handlers. An example that works with the same schema and types is below:
server.get("/data", {
schema: {
headers: TenantHeaderSchema
}
},
createGetDataHandler())
...
function createGetDataHandler() {
return async (request: FastifyRequest<{Headers: TenantHTTPHeader}>,
reply: FastifyReply) => {
const {tenant} = request.headers
Expose decorated fastify server in websocket handler
Currently, taking advantage of one of the use cases of decorations and the plugin system is not possible inside a websocket handler.
Decoration use case from Fastify docs:
fastify.decorate('db', new DbConnection())
fastify.get('/', async function (request, reply) {
reply({hello: await this.db.query('world')})
})
This means that we can not take advantage of other plugins that might have been setup in our Fastify server, and must find another way to expose this functionality to the websocket handler, possibly resulting in duplicated efforts.
I looked at the source of this plugin and understand that it uses its own independent router and therefore, most of the facilities we expect inside a regular request handler are not available. I believe we can expose the Fastify server to the websocket handler with little effort to take advantage of the decorations introduced by other plugins.
In Fastify handlers, this
is bound to the server. I believe we can use the same strategy here, for consistency's sake, though I admit I'm not familiarized with the memory/performance implications.
fastify.get('/', { websocket: true }, function myWSHandler(connection, req) {
// this is bound to Fastify server
this.myPlugin.someFunc()
connection.socket.on('message', message => {
// message === 'hi from client'
connection.socket.send('hi from server')
})
})
Possible implementation?
https://github.com/fastify/fastify-websocket/blob/master/index.js#L55
// ...
router.on('GET', routeOptions.path, (req, _, params) => {
- const result = wsHandler(req[kWs], req, params)
+ const result = wsHandler.call(fastify, req[kWs], req, params)
if (result && typeof result.catch === 'function') {
result.catch(err => req[kWs].destroy(err))
}
})
I'm willing to help with the implementation if you agree that this would be useful.
After client sends ~100 messages of ~160 bytes each to websocket, connection.socket.on('message')
is no longer being triggered.
It seems to be size-dependent, I can send thousands of very short (a few bytes) messages, but only around 100 lines of 160 bytes.
In new dir:
yarn init
yarn add fastify fastify-websocket
Create server.js
:
'use strict'
const fastify = require('fastify')()
fastify.register(require('fastify-websocket'))
fastify.get('/', { websocket: true }, (connection, req) => {
connection.socket.on('message', message => {
// message === 'hi from client'
connection.socket.send('hi from server')
})
})
fastify.listen(5000, err => {
if (err) {
fastify.log.error(err)
process.exit(1)
}
Run node server.js
In another window, run:
wscat --show-ping-pong -c "ws://localhost:5000"
Create a file with ~150 lines of 160 bytes each. Copy-paste contents into wscat.
The server will stop responding.
Should keep returning responses.
The ws added support for native node streams. We can drop websocket-stream and just use that.
3.x.x
No response
unknown
Linux
unknown
When I tried using verifyClient
in my server, Fastify kept sending Reply already sent
errors. And in the ws
documentation, it literally encourages not to use it. I believe this plugin should throw an error or deprecation warning when attempting to use verifyClient
to avoid these issues in the future.
Start a Fastify server using this plugin with a verifyClient
option and logging on.
I believe this plugin should throw an error or deprecation warning when attempting to use verifyClient
to avoid these issues in the future.
I'm kind of loosing my mind over this...
Server-side socket stops accepting messages, even though the connection remains open and it is still able to send messages.
See https://github.com/cope/wtfws for an extremely detail description and ability to clone and repro the issue.
Server-side socket to KEEP accepting messages.
hi,
I need to generate new websocket routes after a post request, yet the client that tries to connect to it will get a 500 error.
I tried to put the route in beforehand, which worked well, but trying to create it on the fly, won't do. Should I be better using the ws package for this?
const createWebsocketRoute = (listenKey) => {
console.log('?CREATE?')
// CRASHES HERE.
fastify.get(`/ws/x`, { websocket: true }, ({socket}, res) => { // needs to close previous socket
console.log('socket connected:', listenKey)
setTimeout(() => {
socket.send(JSON.stringify({
d: "100.00000000",
T: 1573200697068,
E: 1573200697110,
}))
}, 500)
})
}
fastify.route({ method: 'POST', websocket: false, url: '/api/v3/userDataStream', handler: async ({query}, res) => {
listenKey = crypto.createHash('sha256').update(crypto.randomBytes(256)).digest("hex").slice(0, 64)
console.log('new listenKey:', listenKey)
createWebsocketRoute(listenKey)
// CLIENT CRASHES
await new Promise(rs => setTimeout(rs, 1000)) // tried to add some timeout for maybe the route has to start up=?
res.send({listenKey})
}})
As titled, this would be awesome.
At the time of this writing:
Time: 1s
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 96 | 86.36 | 90 | 97.96 | |
index.js | 96 | 86.36 | 90 | 97.96 | 59 |
----------|----------|----------|----------|----------|-------------------|
when I register api module and give it a prefix option and then in the module file I can't set get route without any additional path name fastify.get('/', { websocket: true }, <function>)
and it gives me the error
AssertionError [ERR_ASSERTION]: Method 'GET' already declared for route '/api/'
it works when I add something to get path like fastify.get('/hi', { websocket: true }, <function>)
app.js
fastify.register(require('fastify-websocket'))
fastify.register(require('./api), { prefix: '/api' }
api.js
fastify.get('/', { websocket: true }, <function>)
It should work in websocket. like it works fine without websocket (regular get '/' path)
Currently we wrap every websocket connection in a Node Stream, however it might be handy to not do that in certain cases. We could add an option to avoid that.
const fastify = require('fastify')()
fastify.register(require('fastify-websocket'), {
handle,
stream: false
})
HI!
I have this:
async getRealtimeOrdersByShop(connection, request, params) {
try {
const id = await params.id;
} catch (error) {
throw boom.boomify(error);
}
}
but params returns undefined. What is wrong?
We should be able to add options like perMessageDeflate: false
to the websocket server.
Hey guys,
for faking HTTP requests, there is ab option to inject fake HTTP requests via light-my-request. Looking in the tests of this repo, I can see that Fastify really needs to listen on some port in order to create a WS client connetion.
Is there a way to Inject a WS connection without actually making Fastify server listen on a port?
I'm asking because right now we many HTTP tests using Ava testing framework, that are in many separate files (due to concurrent testing). So far for HTTP testing we were not forcing the Fastify server to listen on any port. Now we are at the point where we want to start cover the WS scenarios with such tests and it seems that we are going to need to actually make Fastify server listen on certain port.
The issue we are seeing is that each test file will need to have a separate port, because they run concurrently. I know it's doable like that, but I'm just currious if it's possible to fake WS connections as well as WS data being sent?
Thanks in advance.
/GJ
From the description and evaluation, uWebSockets.js has higher performance, whether to consider ws/uWebSockets.js as an optional engine
more efficient performance
No response
Any idea for socket.io?
The second parameter of the Global Handler should be ServerRequest instead of ServerResponse.
app.register(require("fastify-websocket"), {
handle: (ws,req) => {
console.log(req); // This should be Request, not Response
wsHandle(ws);
},
options: {
maxPayload: 1048576,
path: "/websocket",
},
});
The second parameter of the Global Handler is ServerRequest
app.register(require("fastify-websocket"), {
handle: (ws,req) => {
console.log(req); // Here is the Request
wsHandle(ws);
},
options: {
maxPayload: 1048576,
path: "/websocket",
},
});
Line 83 in c276f6d
Hi, I just finished implementing routing plugin for WS server. I needed it for declaring multiple handlers and a "404 error" fallback on unregistered routes.
Before publishing on npm, I was wandering if it would be a PR for this repo, instead of creating a new package.
Here is the code: https://github.com/fox1t/fastify-websocket-router
I'll delay publishing till this issue will be closed.
In index.d.ts RouteOptions
is not augmented with wsHandler
, so only short form (fastify.get
) works, full one (the one below with fastify.route
) doesn't compile because of missing wsHandler.
fastify.route({
method: 'GET',
url: '/hello',
handler: (req, reply) => {
// this will handle http requests
reply.send({ hello: 'world' })
},
wsHandler: (conn, req) => {
// this will handle websockets connections
conn.setEncoding('utf8')
conn.write('hello client')
conn.once('data', chunk => {
conn.end()
})
}
})
Dear folks,
I've recently done some research on setting headers within websocket requests.
The idea is to have a websocket connection to a server, while being able to (re-)authenticate via a https-only cookie.
For non-ws requests you would use reply.header(key, value).
The therefore needed reply object is nowhere accessible in the wsHandler.
I've tried creating a fork and include the reply.header() call within the onRoute hook, but neither before or after the reply.hijack() call the inserted call to reply.header() has any effect.
I'm wondering if you can help me?
Further assumptions I have:
I really hope somebody can help me :-)
Kind regard
ulrichrobin
EDIT: Another solution I have come up with would be setting the header on a separate non-ws route, and read the set cookie via request.header['Cookie']. Is this the way my issue is resolved out there yet?
This project uses find-my-way
and is pinned to 3.0.4
. Can you please bump it to 3.0.5
(which is what it is in the main fastify project).
This is currently causing dependency alerts in repo's that use this project due to this:
GHSA-jgrh-5m3h-9c5f
[email protected] allows for extending the WebSocket class.
Issue discussed here: websockets/ws#2007
I forked myself and simply npm i -P ws
, which seems to work fine. However I'm not sure how to navigate the typescript def.
import fastify from 'fastify'
import websockets from 'fastify-websocket'
import WebSocket from 'ws'
class MyWebSocket extends WebSocket {
foo() {
return 'bar'
}
}
const server = fastify()
server.register(websockets, {
options: { WebSocket: MyWebSocket }
})
Hey, I stumbled upon a small issue in my fastify API server... I've registered cors and websocket plugin as well as created some GET/POST routes.... the REST API call all work fine, but as soon as I try to connect to the WebSocket I see some errors from the cors plugin:
"reqId":2,"err":{"type":"FastifyError","message":"Reply was already sent.","stack":"FastifyError: Reply was already sent.
[...]
at ...webserver/node_modules/fastify-cors/index.js:85:129)\n at onRequest (...webserver/node_modules/fastify-cors/index.js:87:3)","name":"FastifyError","code":"FST_ERR_REP_ALREADY_SENT","statusCode":500},"msg":"Reply already sent"}
the websocket client (wscat) says connected though, and communication via the websocket is working as well...
I created a small sample showing the behavior: https://github.com/appelgriebsch/fastify-sample. When doing a GET / it will respond properly with OK and show only info level logs for the server. When connecting to the websocket via wscat /ws you will notice the above mentioned error in the log.
Connecting to the websocket should not lead to an issue in the fastify processing pipeline.
What i'm suggesting is using the route schema available already in fastify
route definition and use it to validate messages payload.
const handleUpgrade = (rawRequest, validate, callback) => {
wss.handleUpgrade(rawRequest, rawRequest[kWs], rawRequest[kWsHead], (socket) => {
wss.emit('connection', socket, rawRequest)
const connection = WebSocket.createWebSocketStream(socket, opts.connectionOptions)
connection.socket = socket
connection.socket.on('message',(message)=>{
// we would need something like so
if(!validate(message)) {
connection.socket.send('RIP')
} else {
connection.socket.emit('parsedMessage', {foo:'bar'}) // we emit a parsed message event
}
})
connection.socket.on('newListener', event => {
if (event === 'message') {
connection.resume()
}
})
callback(connection)
})
}
Constraint here is we cannot unemit
an event on the socket, i propose the parsedMessage
event which is used for the validation. message
event handler will still listen to all the events.
Problem currently is we cannot have access to the ajv-compiler
from the fastify
instance. If we can have access to it on within the instance or the onRoute
hook we can compile the schema from the route option and create the validation function for the websocket connection.
What do you think?
note: also slight change needed in the onRoute
hook
if (routeOptions.websocket || routeOptions.wsHandler) {
let wsSchema = routeOptions.schema // otherwise the onUpgrade fails with invalid body
delete routeOptions.schema
Reviving fastify/help#102
It would be great to have message validations based on the route schemas
No response
Dependabot encountered the following error when parsing your .dependabot/config.yml
:
The property '#/' did not contain a required property of 'update_configs'
The property '#/' contains additional properties ["update_conigs"] outside of the schema when none are allowed
Please update the config file to conform with Dependabot's specification using our docs and online validator.
Hello everyone!
I need a simple example of this plugin to integrate to my chat application.
Can someone help me?
Thanks!
fastify-websocket cant be used in a typescript file
fastify supports typescript but not websocket
npm install @types/fastify-websocket
Adding readableObjectMode
as an option for fastify-websocket.
I noticed that currently in the documentation, it says that fastify-websocket supports objectMode
as an option for [ws](https://github.com/websockets/ws/)
, but objectMode is not listed in the ws options documentation.
It seems reasonable to add readableObjectMode as an option to fastify-websocket as it is an option for createWebSocketStream
and achieves a similar goal. It may also make sense to allow users to change other connection (duplex) options like readableHighWaterMark
and writableHighWaterMark
, but I haven't needed those options yet.
There is currently no way to set readableObjectMode
and this can lead to a TypeError for certain WebSocket applications, specifically:
TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of
Buffer or Uint8Array. Received an instance of ArrayBuffer
That being said, this issue which led to the inclusion of readableObjectMode
as an option for createWebSocketStream
suggests there are issues with objectMode and backpressure handling, and the option could be a footgun.
This error also didn't occur when using express-ws
so I wonder if it's related to how Fastify works as the issue also highlights a bug in Node.js core. I noticed that express-ws
doesn't seem to call createWebSocketStream at all and so I'm curious why there is a difference in implementation and that might reflect if this feature is needed.
fastify.register(require('fastify-websocket'), {
options: { maxPayload: 1048576 },
connectionOptions: { readableObjectMode: true } // can include other duplex options
})
Then these options can be passed to WebSocket.createWebSocketStream
const connection = WebSocket.createWebSocketStream(socket, opts.connectionOptions)
Compiling my fastify server using Typescript, I am getting an error message pointing out the missing the 'ws' module declaration file. When manually installing the corresponding DefinitelyTyped (@types/ws), the error disappears.
[05:36:04] Starting compilation in watch mode...
node_modules/fastify-websocket/index.d.ts:5:28 - error TS7016: Could not find a declaration file for module 'ws'.
'D:/_software/electronic-publishing/social/node_modules/ws/index.js' implicitly has an 'any' type.
Try `npm install @types/ws` if it exists or add a new declaration (.d.ts) file containing `declare module 'ws';
5 import * as WebSocket from 'ws';
[05:36:07] Found 1 error. Watching for file changes.
I believe that this dependency should be determined in fastify-websocket and not directly in my project.
node version: 14.10.1
fastify version: >=3.8.0
os: Windows
project dependencies:
"dependencies": {
"dotenv": "^8.2.0",
"fastify": "^3.8.0",
"fastify-compress": "^3.4.0",
"fastify-cors": "^4.1.0",
"fastify-helmet": "^5.0.3",
"mercurius": "^6.2.0",
"pg": "^8.4.2",
"pg-format": "^1.0.4",
"uuid": "^8.3.1"
},
"devDependencies": {
"@types/node": "^14.14.7",
"@types/pg": "^7.14.6",
"@types/pg-format": "^1.0.1",
"@types/uuid": "^8.3.0",
"typescript": "^4.0.5"
}
I would like to see if a request was websocket in the onRequest
hook, maybe through a req.ws
boolean.
I have a chat which uses the full declaration syntax with the websocket. However, I want my request to log differently if the request was a websocket.
Maybe like this:
fastify.addHook("onRequest", async (req, res) => {
if (req.ws) { //this is if it's a websocket
req.log.info({req}, "websocket tryna connect");
} else {
req.log.info({req}, "regular old request");
}
});
Importing fastify-websocket
using typescript leads to undefined
Steps to reproduce the behavior:
Simply importing fastify-websocket
and attempt to register it
import fastify, { FastifyInstance } from 'fastify'
const server: FastifyInstance = fastify({})
import fastifyWebSocket from 'fastify-websocket'
server.register(fastifyWebSocket)
server.listen(8080, (err, address) => {
// ...
})
After building and attempting to run, fastify
will throw an error,
Error: plugin must be a function or a promise
Doing a console.log
reveals fastifyWebSocket
is undefined
Long story short: It looks to me like parametric routes did not got parsed anymore.
fastify.get('/pools/:user', { websocket: true }, async (connection, req) => {
})
thats pretty odd
3.24.1
4.0.0
14.15.4
macOS
12.0.1
It seem impossible to access the decarated properties websocketServer (the istance of the WS lib).
I need it to access currently connected clients.
const fastify = require("fastify")();
fastify.register(require('fastify-websocket'));
console.log(server.websocketServer); //=> prints undefined
server.websocketServer is the real WebSocketServer instance (from ws lib)
The setting noServer = true
in options has no effect. I was trying to implement a websocket authorization, based on the examples provided in the ws repository. To handle the upgrade of the connection, the setting noServer
must be set to true
. If i do so in fastify-websocket, this has no effect.
The issue is, that ws checks for the setting, but this is overruled by a check of the property server
of the ws instance. This property is always set in fastify-websocket regardless of the noServer
setting.
See ws/lib/websocket-server.js Line 81
{
... check for parmeter noServer
} else if (options.server) {
this._server = options.server;
}
if (this._server) {
...
This leads to the following error in my case:
Error: server.handleUpgrade() was called more than once with the same socket, possibly due to a misconfiguration
at WebSocketServer.completeUpgrade (/workspace/backend/node_modules/ws/lib/websocket-server.js:267:13)
at WebSocketServer.handleUpgrade (/workspace/backend/node_modules/ws/lib/websocket-server.js:245:10)
at Server.<anonymous> (/workspace/backend/src/index.ts:197:28)
at Server.emit (events.js:326:22)
at Server.EventEmitter.emit (domain.js:486:12)
at onParserExecuteCommon (_http_server.js:644:14)
at onParserExecute (_http_server.js:577:3)
at HTTPParser.callbackTrampoline (internal/async_hooks.js:129:14) uncaughtException
Steps to reproduce the behavior:
Set the parameter noServer to true
// Paste your code here
The server property of ws should only be set when noServer
is null or false.
// Paste the expected results here
I'm trying to setup a graceful exit strategy by closing connections to other services (eg: database, redis cache) in an onClose
hook, using fastify-graceful-shutdown
.
However, when there are active websocket connections, the onClose
hooks are never called, even though fastify.close()
was called upon reception of the exit signal by fastify-graceful-shutdown
.
This is probably linked to the fact that graceful shutdown waits for requests to complete (while refusing new ones, which works ok), and websocket requests never actually complete on their own..
Using this example on CodeSandbox:
onClose
log line is not printed out.Server code:
const Fastify = require("fastify");
const server = Fastify({ logger: true });
// The order does not seem to matter here:
server.register(require("fastify-graceful-shutdown"));
server.register(require("fastify-websocket"));
server.get("/foo", { websocket: true }, (connection, req) => {
const socketID = req.headers["sec-websocket-key"];
server.log.info({
msg: "Client connected",
socket: socketID
});
connection.socket.on("message", message => {
server.log.info({
msg: message,
socket: socketID
});
});
connection.socket.on("close", () => {
server.log.info({
msg: "Client disconnected",
socket: socketID
});
});
});
server.addHook("onClose", (server, done) => {
server.log.info("in the onClose hook");
done();
});
server.listen({ port: 8080 });
Test client code:
const WebSocket = require('isomorphic-ws')
const client = new WebSocket('wss://uvgpw.sse.codesandbox.io/foo')
client.on('open', () => {
console.log('Connected')
client.send('foo')
})
client.on('close', () => {
console.log('Disconnected')
})
The onClose
hook should be called when fastify.close()
is called.
Closing the open websocket connections could be done manually here using whatever strategy makes sense for the app (sending a batch of data, a special message to the client before closing the connection...).
fastify-graceful-shutdown
: 2.0.1fastify-websocket
: 1.1.0I have no problems with the connection, all clients connect without problem. My question is how can I send a message to everyone at the same time since I have tried the examples of the 'ws' library but I have not succeeded. Someone has an example code, here I leave part of my code on the server.
fastify.route({
method: 'GET',
url: '/',
handler: (req, reply) => {
reply.send({ ruta: 'OK' })
},
wsHandler: (conn, req, params) => {
conn.setEncoding('utf8')
conn.on('data', chunk => {
console.log(chunk)
conn.socket.send('message OK')
})
}
})
Using typescript, as I wanted to access websocketServer
the type says that it is not defined. I am wondering If the type is defined.
This gives error:
fastify.websocketServer.clients
So I opt to any
which works cause it is there.
(fastifyServer as any).websocketServer.clients
@fox1t as I saw that you implemented the types, can you help me on the matter, please?
Migrate or offer an option for socketio v3
Right now, connection from socketio.client v3 results in
Error: It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible
Bootstrap is done with the following parameters
fastify.register(require('fastify-websocket'), {
function(conn) {
conn.pipe(conn); // creates an echo server
},
options: {
maxPayload: 1048576, // we set the maximum allowed messages size to 1 MiB (1024 bytes * 1024 bytes)
path: '/ws',
verifyClient: (info, next) => {
if (!info.req.headers['x-nao-auth']) {
logg(`x-nao-auth header not found > `, info.req.headers);
// -->Drop: if header isn't there
return next(false);
}
next(true) // the connection is allowed
}
}
});
And connecting from the client side
this.io = io(environment.API.wsUsers.url, {
// path: environment.API.wsUsers.url + environment.API.wsUsers.options.path,
path: environment.API.wsUsers.options.path,
v3 adds a lot of value with the shared pipes and ability to use one connection with multiple namespaces
Feature will be use same as v2 but better :)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.