caddy icon indicating copy to clipboard operation
caddy copied to clipboard

reverseproxy: Cleaner close of websocket connections (and SSE)

Open mholt opened this issue 3 years ago • 7 comments

Based on info from https://github.com/gorilla/websocket/issues/316#issuecomment-353987393, we can probably be closing web sockets more cleanly.

mholt avatar May 05 '22 18:05 mholt

I read reverse_proxy code and findout if response is 101, reverse_proxy will do a hijack and take it from there.. Per http.Server documentation, reverse_proxy should register a shutdown function. I know how to close websocket connection, but have no experience with sse or h2c.

Maybe reverse_proxy needs to keep track of hijacked connections and send a goaway frame to websocket at least. I don't know how other protocol does graceful shutdown. Protocol upgrade can be found in the upgrade header.

WeidiDeng avatar Jun 13 '22 09:06 WeidiDeng

Yeah exactly, that's my same reading of it, we need to keep track of websocket connections in its own list and send close frames. I'm not sure how best to write the close frame logic though so we could use some help to get that right.

We can do this via Cleanup() on the reverse proxy module, which is called when the config is being shut down or reloaded.

I also think we should consider adding a grace timeout config in the reverse proxy module itself to close off other hijacked connections that are of unknown type.

Maybe for SSE we can just always close it right away because it should rarely be harmful to do so, but it could hurt if we close other kinds of streams where the frontend/backend of the app doesn't have logic to recover gracefully.

francislavoie avatar Jun 13 '22 12:06 francislavoie

Can I pick this up?

deepto98 avatar Jun 20 '22 03:06 deepto98

Sure, PRs always welcome!

francislavoie avatar Jun 20 '22 04:06 francislavoie

Yeah, we'd love the assistance!

mholt avatar Jun 20 '22 04:06 mholt

I've made progress on this to the point where I think I have it working. I was not experiencing any hanging config reloads before my changes, so if anyone is having that with WebSockets, I'd like to know. What I did experience, however, was that WebSocket connections were not closed on config reloads, which I think they should be.

So I have implemented that Caddy will send its own Close control messages when a config reload or shutdown is taking place. In my testing, this cleanly closes the connections with the client and the backend, whereas before the connections would have an abnormal close on exit. (It is a best-effort attempt though, I don't wait for a returned Close frame...)

As an aside, grace_period seemed irrelevant in my testing. Probably because http.Server.Shutdown() doesn't do anything with hijacked connections.

mholt avatar Jul 14 '22 18:07 mholt

I've pushed my current efforts (which is the result of rewriting it about 3 times) to #4895. @WeidiDeng if you're interested, please feel free to try it out!

In my testing, the change successfully closes the connection that was left open before. It is closed between client and backend. For WebSocket connections specifically, the Close frame is now sent by Caddy to give the backend and the client and opportunity to close gracefully (code 1001 instead of 1006).

If interested parties could please try out my changes in staging and production, that would be great!

mholt avatar Jul 17 '22 05:07 mholt