Start using FastifyRequest objects for the upgrade request so hooks fire and decorators can be used
See original GitHub issue🚀 Feature Proposal
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.
Motivation
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.
How
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.
Context
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?
Issue Analytics
- State:
- Created 3 years ago
- Reactions:4
- Comments:14 (9 by maintainers)
I see what you mean, it does remove some noise for creating websocket handlers, but I think the mission of this library should be to make more possible as opposed to just making the common case easier. I think some folks might want access to the
connection
object instead of just the socket, and I think the PRs linked above remove the need to work with theparams
as a separate object, and would pass in aFastifyRequest
with the params already set up fine.@adminy can you be more specific about what you’d like to see? I don’t really understand.
I kind of disagree. That’s how they work, there’s no other way to initiate them, you can’t around this. Let’s say you want to build authentication for websocket connections to your server. If your server already serves normal authenticated HTTP requests, you already have some way to look at an HTTP request and figure out who is making it and if you trust them, lets say through cookies and
fastify-secure-session
. A really easy way to authenticate websocket requests would be to use the exact same mechanism, and look at the cookies for the incoming upgrade request. You can only do that because there’s a request, and if you are given the request. Forcing users of this library to re-build authentication or any of their middleware/plugin stack in a websocket-specific way just because the transport is different seems unnecessary and wasteful.