Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reverseproxy: Wait for both ends of websocket connections to close #6175

Merged
merged 1 commit into from
Apr 15, 2024

Conversation

WeidiDeng
Copy link
Member

Related to 6173.

Websockets will show size 0 even if there are data written because the hijacked connection implements both WriterTo and ReaderFrom, and stats are only updated when the copying is done.

A log entry is like this:

{"level":"info","ts":1710675070.8584435,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"111.181.20.83","remote_port":"12070","client_ip":"111.181.20.83","proto":"HTTP/1.1","method":"GET","host":"example.com","uri":"/random_path","headers":{"Accept-Language":["zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6"],"Connection":["Upgrade"],"Pragma":["no-cache"],"Cache-Control":["no-cache"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"],"Upgrade":["websocket"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Websocket-Extensions":["permessage-deflate; client_max_window_bits"],"Origin":["null"],"Sec-Websocket-Version":["13"],"Sec-Websocket-Key":["/8yHPgBZyvYC8CD39WeZRA=="]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"http/1.1","server_name":"example.com"}},"bytes_read":3092,"user_id":"","duration":4.455078925,"size":0,"status":101,"resp_headers":{"Upgrade":["websocket"],"Connection":["Upgrade"],"Sec-Websocket-Accept":["GZiqDfESG0QwR+aK3v0Cqop5BR4="],"Server":["Caddy"],"Alt-Svc":["h3=\":63933\"; ma=2592000,h3=\":443\"; ma=2592000"],"Strict-Transport-Security":["max-age=31536000;"]}}

Wait outside the function so connection cleanup is not affected.

@francislavoie francislavoie changed the title http: reverse_proxy wait for both ends of websocket connections to close reverseproxy: Wait for both ends of websocket connections to close Mar 18, 2024
Copy link
Member

@francislavoie francislavoie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but concurrency stuff like this isn't one of my strengths. Maybe @mholt should take a look before merging.

@francislavoie francislavoie added the bug 🐞 Something isn't working label Mar 18, 2024
@francislavoie francislavoie added this to the v2.8.0 milestone Mar 18, 2024
Copy link
Member

@mholt mholt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic looks sound. But before I totally approve it, I have a concern about the implications of blocking in finalizeResponse(). I think this blocks the entire HTTP request. (But just this request.) Wouldn't that be problematic?

h.handleUpgradeResponse(logger, rw, req, res)
var wg sync.WaitGroup
h.handleUpgradeResponse(logger, &wg, rw, req, res)
wg.Wait()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This blocks -- not sure if that's actually a good idea?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds a little bit of delay so that statistics are properly updated. Both ends of the connection are closed when handleUpgradeResponse returns. If this blocks, go has a serious issue where net.Conn.Close doesn't unblock Read or Write.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so you're saying that h.handleUpgradeResponse() was already blocking, and wg.Wait() waits until the actual conns are Close()ed, but otherwise it's basically the same amount of blocking.

In that case, this LGTM.

I just couldn't remember if this goroutine was returning while the hijacked connection kept running to do its copy, or if we blocked in there, but it looks like handleUpgradeResponse has a select that blocks at least for most of the time.

So yeah, if this patch works, and doesn't block much longer, then I think we can merge it.

Thanks!!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, does the size still log as 0, with this patch?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this patch, sometimes size logs as 0. After this, none does.

@mholt mholt merged commit b40cacf into master Apr 15, 2024
25 checks passed
@mholt mholt deleted the reverse-proxy-websocket-size branch April 15, 2024 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐞 Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants