WinHttpHandler: Read HTTP/2 trailing headers#46602
WinHttpHandler: Read HTTP/2 trailing headers#46602JamesNK wants to merge 1 commit intodotnet:masterfrom
Conversation
|
Tagging subscribers to this area: @dotnet/ncl Issue DetailsRead HTTP/2 trailing headers when using WinHttpHandler. Notes:
|
| // Only load response trailers if: | ||
| // 1. HTTP/2 or later | ||
| // 2. Response trailers not already loaded | ||
| if (_readTrailingHeaders || _responseMessage.Version < WinHttpHandler.HttpVersion20) |
| protected static Frame MakeDataFrame(int streamId, byte[] data, bool endStream = false) => | ||
| new DataFrame(data, (endStream ? FrameFlags.EndStream : FrameFlags.None), 0, streamId); | ||
|
|
||
| [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))] |
There was a problem hiding this comment.
OS version condition on these tests?
....WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj
Outdated
Show resolved
Hide resolved
antonfirsov
left a comment
There was a problem hiding this comment.
I see that #44778 still has the api-needs-work label. Even if there are no new API additions technically, these are behavioral changes with API aspects, so we should some sort of consensus before proceeding. (Maybe this has already happened and I'm just missing something -- @karelz ?)
The proposed solution has several oddities which are very concerning IMO:
- Behavior differs depending on OS version in a way that is not transparent to the users. (If my understanding is correct: we fill some collection when
WINHTTP_QUERY_FLAG_TRAILERSis supported, otherwise we don't.) - Behavior differs depending on the target (what collection to fill)
These differences are far from being obvious. The question we should raise to ourselves is: how would we document / communicate such an API to the public? How would we respond to critiques claiming the way we implemented this is overcomplicated?
@geoffkizer any thoughts?
src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpResponseParser.cs
Outdated
Show resolved
Hide resolved
| else | ||
| { | ||
| throw WinHttpException.CreateExceptionUsingError(lastError, nameof(Interop.WinHttp.WinHttpQueryHeaders)); | ||
| if (!(lastError == Interop.WinHttp.ERROR_INSUFFICIENT_BUFFER || lastError == Interop.WinHttp.ERROR_WINHTTP_HEADER_NOT_FOUND)) |
There was a problem hiding this comment.
Is ERROR_WINHTTP_HEADER_NOT_FOUND here to fail silently when WINHTTP_QUERY_FLAG_TRAILERS is unsupported?
There was a problem hiding this comment.
No, it is to handle HTTP responses that don't have any trailers. A response will always have headers, but trailers are optional.
I think it is better to have an OS version check to see whether trailers are supported. That would determine whether the call is made to begin with.
How are Windows version checks does in this library? (or other runtime libraries)
There was a problem hiding this comment.
In this library these checks are unprecedental, setting unsupported options usually leads to WinHttpException. I've seen checks on Environment.OSVersion in other BCL libs though .
There was a problem hiding this comment.
What if WinHttp decides to backport the feature to earlier OS versions? I would feel more comfortable with established pattern of relying on WinHttpException. Is that possible?
There was a problem hiding this comment.
With the current design, this feature is implicit (filling the collection whenever possible), so throwing is not an option.
However I think that checking the error code after the first WinHttpQueryHeaders shall be a sufficient (and actually better) solution.
There was a problem hiding this comment.
One thing to consider here --- we should be very careful about having an API that, depending on OS version:
a) works as expected.
b) appears to work, but has an empty collection with zero notice to user.
This will be very fragile.
There was a problem hiding this comment.
@scalablecory I tried to address this in my proposal in #44778 (comment). I suggest to discuss the API under the issue, comments and ideas are more than welcome there.
There was a problem hiding this comment.
I discussed OS support detection for trailers with WinHttp team and this is their suggestion:
How about this for detection.
You try to query WINHTTP_OPTION_STREAM_ERROR_CODE from you session handle.
If it fails with ERROR_INVALID_PARAMETER, there is no support for trailers.
Otherwise, it should fail ERROR_WINHTTP_INCORRECT_HANDLE_TYPE, as the option cannot be queried from session handles.
They prefered doing that over hardcoding an OS version number because WinHttp trailer support could be backported to more Windows versions.
There was a problem hiding this comment.
You try to query WINHTTP_OPTION_STREAM_ERROR_CODE from you session handle.
I guess these are distinct features which have been introduced in the same release right? What if they get misaligned because trailing headers get backported to an earlier version?
There was a problem hiding this comment.
I presume so. This is the WinHttp team's suggestion. They recommended against checking OS version.
src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpResponseParser.cs
Show resolved
Hide resolved
|
Thanks @JamesNK for your PR!
I agree we need to have consensus on the API shape first. Even though it is "just" a property bag, we need to make sure it is aligned with our strategy for all handlers (incl. naming convention of the properties) and IMO we should go through official API review first. I think it would be best to have the API discussion on the issue #44778 rather than in the PR ... thoughts? |
|
I tested this PR using the gRPC interop tests. The interop tests use WinHttpHandler + Grpc.Net.Client to send end-to-end gRPC calls. I can confirm that all the interop tests that send unary (simple request/response) and server streaming gRPC calls succeed with these changes, which is what I expected. The tests succeed on both .NET 5 and .NET Framework. |
....WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj
Show resolved
Hide resolved
antonfirsov
left a comment
There was a problem hiding this comment.
Not an extensive review yet, just a few remarks.
src/libraries/System.Net.Http.WinHttpHandler/ref/System.Net.Http.WinHttpHandler.cs
Outdated
Show resolved
Hide resolved
|
|
||
| // Write that data out to the output stream | ||
| #if NETSTANDARD2_1 | ||
| await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false); |
There was a problem hiding this comment.
Are there any observable advantages from using this overload of Stream.WriteAsync explicitly? I would expect that the performance of (most) underlying implementations is identical. Or am I missing something?
There was a problem hiding this comment.
There is an error when not calling it on netstandard2.1:
error CA1835: Change the 'WriteAsync' method call to use the 'Stream.WriteAsync(ReadOnlyMemory, CancellationToken)' overload
| else | ||
| { | ||
| throw WinHttpException.CreateExceptionUsingError(lastError, nameof(Interop.WinHttp.WinHttpQueryHeaders)); | ||
| if (!(lastError == Interop.WinHttp.ERROR_INSUFFICIENT_BUFFER || lastError == Interop.WinHttp.ERROR_WINHTTP_HEADER_NOT_FOUND)) |
There was a problem hiding this comment.
You try to query WINHTTP_OPTION_STREAM_ERROR_CODE from you session handle.
I guess these are distinct features which have been introduced in the same release right? What if they get misaligned because trailing headers get backported to an earlier version?
e56ee37 to
df5dcd1
Compare
|
/azp run runtime |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
I'm closing this PR so it doesn't show up in stale PR reports. A replacement PR will follow in a day or two. |
Fixes #44778
Read HTTP/2 trailing headers when using WinHttpHandler.
Notes:
HttpResponseMessage.TrailingHeadersin netstandard2.1. With older targets the trailers are stashed inHttpRequestMessage.Properties.