-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
WinHttpHandler: Read HTTP/2 trailing headers #46602
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OS version check here?
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OS version condition on these tests?
....WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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_TRAILERS
is 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
{ | ||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is ERROR_WINHTTP_HEADER_NOT_FOUND
here to fail silently when WINHTTP_QUERY_FLAG_TRAILERS
is unsupported?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
break; | ||
} | ||
Debug.Assert(bytesRead > 0); | ||
|
||
// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
{ | ||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.TrailingHeaders
in netstandard2.1. With older targets the trailers are stashed inHttpRequestMessage.Properties
.