Skip to content

feat: h2 server support#96

Open
lukeo3o1 wants to merge 7 commits into
soheilhy:masterfrom
lukeo3o1:h2-support
Open

feat: h2 server support#96
lukeo3o1 wants to merge 7 commits into
soheilhy:masterfrom
lukeo3o1:h2-support

Conversation

@lukeo3o1
Copy link
Copy Markdown

Fix #95

Comment thread matchers.go
// SETTINGS again.
if f.IsAck() {
// Avoid causing golang.org/x/net/http2.serverConn.unackedSettings PROTOCOL_ERROR
sr.discard()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hi @lukeo3o1, I don't understand why call sr.discard here. The purpose of bufferedReader as I understand is that it keeps all data read from the connection to not miss anything for upper level reader of the connection. Am I right? I'm just curious and not much familiar with http2 protocol.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bypass https://cs.opensource.google/go/x/net/+/refs/tags/v0.30.0:http2/server.go;l=1738-1747

Thanks for reply! I can see the point. Is it because there is a bufferreader which causes the ack_mystery error or sr.discard is just a mitigation for that problem? I thought the bufferreader should be transparent for later reader of the connection.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

In fact, it's not the bufferedReader that causes the issue. Rather, some clients will wait to receive the SETTINGS frame first, and once the SETTINGS frame is sent, the client will immediately respond with an ACK frame to confirm receipt. Without using sr.discard as a workaround, it would lead to the issue described in https://cs.opensource.google/go/x/net/+/refs/tags/v0.30.0:http2/server.go;l=1738-1747.

The purpose of sr.discard is to handle the ACK frame response and prevent it from being passed to the application layer, as the ACK frame does not contain meaningful data but is merely used to synchronize the protocol state. Without using sr.discard to discard the ACK frame, the ACK might enter subsequent processing, leading to logical errors or protocol inconsistencies. This is why this operation is necessary to bypass potential issues.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I see. Thanks for your explaination.It's more complicated than I thought.

Copy link
Copy Markdown

@hulb hulb Oct 17, 2024

Choose a reason for hiding this comment

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

I look through the start process of http2 server (https://cs.opensource.google/go/x/net/+/refs/tags/v0.30.0:http2/server.go;l=916-933). The server always sends setting frame first and wait for preface of clients. It seems weird that when received setting frame without ack a setting frame instead of a ack setting frame is sent to the client. Because of it cilents need to send back ack setting frame.And bufferreader keeps the ack setting frame which is extra for the application layer.

Maybe It is not necessary to send setting frame(without ack flag) here and ack setting frame wouldn't be read at this time.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Apologies for the late reply — I overlooked this thread earlier.

Thanks for digging into the server startup flow! However, the SETTINGS frame sent here is actually required by the HTTP/2 specification (RFC 7540 §3.5). Both endpoints must send a SETTINGS frame as part of the connection preface before any other frames are exchanged.

In cmux's matching phase, we need to send the SETTINGS frame to keep the HTTP/2 handshake going — some clients strictly wait for the server's SETTINGS frame before sending further data. Without sending it, those clients would stall, and cmux would never read enough data to complete the protocol matching.

Because we send a valid SETTINGS frame, compliant clients will immediately reply with a SETTINGS ACK (as the spec requires). That ACK ends up buffered in bufferedReader. If we don't call sr.discard() to consume it, the ACK frame gets passed to the real HTTP/2 server handler, which then triggers the unackedSettings PROTOCOL_ERROR at server.go L1738-1747 — because the handler sees an ACK for a SETTINGS it didn't send itself.

So both the SETTINGS send and the sr.discard() are necessary and intentional. Hope this clarifies things!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

http2 matcher not working with net/http

2 participants