-
Notifications
You must be signed in to change notification settings - Fork 10k
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
Allow the encoder to split headers across frames #4722
Comments
Hello, I would like to take a look at the issue. Context
Brief recapAs already stated by @Tratcher, if an header doesn't completely fit inside the payload of the current frame, it will be written in the next continuation frame, leaving part of the current frame payload "empty" (or triggering the bug in case the header itself is bigger than the whole payload of a frame). Speculation on possible solutions1.
Impact 2.Another solution would be to change the current design and use something like an observer pattern where the Impact There might be way better ways to solve the issue, I'm looking forward to hear from the team, if the team still see value in improving this part. Thank you. |
As you noticed, this issue is pretty complicated for a fairly minor encoding optimization. You're welcome to explore it and see if it can be done without being too disruptive/complex/slow. You may also want to wait another week or so because there's some ongoing refactoring in this area for #30235. |
I agree. Thanks for the heads up. While I wait for the refactoring I will think about it a little more, but if I cannot come up with something smaller I'll focus on other issues. |
I can confirm the issue is still present: System.Net.Http.HPack.HPackEncodingException: Failed to HPACK encode the headers.
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPackHeaderWriter.EncodeHeadersCore(DynamicHPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span`1 buffer, Boolean throwIfNoneEncoded, Int32& length) in D:\repos\aspnetcore\src\Servers\Kestrel\shared\HPackHeaderWriter.cs:line 121
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPackHeaderWriter.ContinueEncodeHeaders(DynamicHPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span`1 buffer, Int32& length) in D:\repos\aspnetcore\src\Servers\Kestrel\shared\HPackHeaderWriter.cs:line 76
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2FrameWriter.FinishWritingHeaders(Int32 streamId, Int32 payloadLength, Boolean done) in D:\repos\aspnetcore\src\Servers\Kestrel\Core\src\Internal\Http2\Http2FrameWriter.cs:line 561
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2FrameWriter.WriteResponseHeadersUnsynchronized(Int32 streamId, Int32 statusCode, Http2HeadersFrameFlags headerFrameFlags, HttpResponseHeaders headers) in D:\repos\aspnetcore\src\Servers\Kestrel\Core\src\Internal\Http2\Http2FrameWriter.cs:line 490 |
Looking at this issue, I also came to a similar conclusion as suggested by @gfr-g 's in the 1st proposal. I'm not sure on changing My thinking is: Does my thinking sound reasonable? Any objections to go ahead with a PR? Any other suggestions / considerations to take before I get my hands dirty? |
|
Does it worth exploring and opening a PR that touches |
@ladeak Thanks for looking into this! I wish I could give you a more concrete answer, but I'm not familiar enough with this code to have an intuition about whether your approach is correct. Some thoughts:
Hope that helps. Please let us know if still like to tag this on so we can update the issue appropriately. |
When the collection of headers is too large it should already be splitting into continuation frames. The problem now is individual headers, which can be changed to split across frames. |
Thank you for the feedback. I can confirm what @Tratcher says is correct. |
I don't think this is an option available here, as I think (not measured) the copy of serialized header bytes will cause a perf slowdown. |
Having a slow path that does the extra allocation might be a way forward, but we'd have to consider whether the client could force that code path to be taken and possibly DoS the server. |
Another option could be being smart and splitting the encoded header itself and only writing the current corresponding part of it to the stream, but it means encoding multiple time (no extra allocation but does not help against a DoS). |
I am working towards a solution. Currently working on some tests and benchmarks. What I realize (and wonder) if H/3 faces a similar error. I have not yet read the HTTP3 specs yet, but throw new QPackEncodingException("TODO sync with corefx" /* CoreStrings.HPackErrorNotEnoughBuffer */); |
I agree that http/3 is probably affected. I suspect this bug pre-dates http/3 support. 😆 |
The HPackEncoder has a potential infinite loop today.
https://github.com/aspnet/KestrelHttpServer/blob/55e5e564226e7b27742c91f46ea71841071e4ac3/src/Kestrel.Core/Internal/Http2/HPack/HPackEncoder.cs#L35-L42
EncodeHeader returns false if it does not have enough space to encode the value. If this header was larger than max frame size then no data will be available for the frame at all and length will be 0. The server will be stuck in an infinite loop sending zero length continuation frames.
aspnet/KestrelHttpServer#2893 added an exception to break the infinate loop, but it did so by throwing and very ungracefully aborting the socket.
Proposal: The spec allows splitting encoded headers across frames. This would allow the encoder to pack frames more densely and always make progress.
Priority: Not urgent, the response header would need to be larger than the max frame size (16kb). The client and server can also raise that frame size. Implementing real response header compression would allow even larger values before hitting the frame size limit.
The text was updated successfully, but these errors were encountered: