Closed Goldziher closed 10 months ago
Take a look at gws.Conn{}.Session()
gws.Conn{}.Session()
Thanks.
Do you mean as an alternative to using context?
I can store in the concurrent map values using session - to save the session object into the session storage, etc.
It will work as a workaround, but I was hoping for a solution more in line with the conventional use of context.
gws encapsulates the readMessage loop, which can only read data from *gws.Conn in the event callback method. I wouldn't consider exporting the readMessage method, the disruption to the existing API design would be too great.
Too many ways to accomplish the same thing can cause problems for some users.
Well, that may be the case. But context is a part of the standard library and API for Golang these days.
Anyhow, passing context via session storage causes an issue - namely, the context is canceled:
func (handler) OnMessage(socket *gws.Conn, message *gws.Message) {
defer func() {
if err := message.Close(); err != nil {
log.Error().Err(err).Msg("failed to close message")
}
}()
if message.Data != nil && message.Data.Len() > 0 && message.Opcode == gws.OpcodeText {
// We are retrieving the request context we passed into the socket session.
value, exists := socket.Session().Load("context")
if !exists {
log.Error().Msg("failed to load context from session")
socket.WriteClose(statusWSServerError, []byte("invalid context"))
return
}
ctx := value.(context.Context)
if ctx.Err() != nil {
panic(ctx.Err())
}
// .... code will not reach here because the context is cancelled.
}
func promptTestingWebsocketHandler(w http.ResponseWriter, r *http.Request) {
socket, err := upgrader.Upgrade(w, r)
if err != nil {
log.Error().Err(err).Msg("failed to upgrade connection")
apierror.InternalServerError().Render(w, r)
return
}
socket.Session().Store("context", r.Context())
go socket.ReadLoop()
}
You are talking about context.Context, sorry for my mistake.
Gws pursues extreme performance and won't support context.Context, it has too much overhead. You can use SetDeadline instead.
ok, thanks for clarifying this
If you don't care about performance loss, you can write your own.
func IsCanceled(ctx context.Context) bool {
select {
case <-ctx.Done():
return true
default:
return false
}
}
func WriteWithContext(ctx context.Context, socket *gws.Conn, op gws.Opcode, p []byte) error {
if IsCanceled(ctx) {
return ctx.Err()
}
ech := make(chan error)
go func() {
ech <- socket.WriteMessage(op, p)
}()
select {
case err := <-ech:
return err
case <-ctx.Done():
return ctx.Err()
}
}
If you don't care about performance loss, you can write your own.
func IsCanceled(ctx context.Context) bool { select { case <-ctx.Done(): return true default: return false } } func WriteWithContext(ctx context.Context, socket *gws.Conn, op gws.Opcode, p []byte) error { if IsCanceled(ctx) { return ctx.Err() } ech := make(chan error) go func() { ech <- socket.WriteMessage(op, p) }() select { case err := <-ech: return err case <-ctx.Done(): return ctx.Err() } }
Thanks, for now, I simply extract the values I need from context and use session storage to pass them. This, though, is also not so performant in the end. I don't need to use cancellation - rather, context is used to pass data from middleware etc. The websocket is mounted on a sub path inside an existing application, and there are layers of authentication and authorization that inject values from the DB into the context.
Hi there,
First off - thanks for this great package. I started building using it, and it works very well so far.
Now, my issue is - how can I access the request context inside a handler.
Lets say I followed the example in the readme:
Now, I have logic implemented inside my
OnMessage
receiver. I can access the socket and the message, but I cannot access the original request and any data attached to it. I also cannot access the context propagated from elsewhere.In my case, I am mounting the WebSocket from this library on a route on an existing HTTP server. Specifically, using go-chi as a router, but it doesn't matter.
I have values injected into the request via middleware, such as user session from the database, parsed URL parameters, etc. I want to be able to pass these into the WebSocket handler in some way - best by using context.
Is there any such way or is it perhaps a feature that will be implemented in the near future?