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

WinHttpHandler: Read HTTP/2 trailing headers #46602

Closed

Conversation

JamesNK
Copy link
Member

@JamesNK JamesNK commented Jan 5, 2021

Fixes #44778

Read HTTP/2 trailing headers when using WinHttpHandler.

Notes:

  • Adds netstandard2.1 target.
  • Loads trailers into HttpResponseMessage.TrailingHeaders in netstandard2.1. With older targets the trailers are stashed in HttpRequestMessage.Properties.
  • Requires modern version of Windows. Should the OS version be checked to avoid using new option on older Windows versions?

@ghost
Copy link

ghost commented Jan 5, 2021

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

Read HTTP/2 trailing headers when using WinHttpHandler.

Notes:

  • Adds netstandard2.1 target.
  • Loads trailers into HttpResponseMessage.TrailingHeaders in netstandard2.1. With older targets the trailers are stashed in HttpRequestMessage.Properties.
  • Requires modern version of Windows. Should the OS version be checked to avoid using new option on older Windows versions?
Author: JamesNK
Assignees: -
Labels:

area-System.Net.Http

Milestone: -

// Only load response trailers if:
// 1. HTTP/2 or later
// 2. Response trailers not already loaded
if (_readTrailingHeaders || _responseMessage.Version < WinHttpHandler.HttpVersion20)
Copy link
Member Author

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))]
Copy link
Member Author

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?

Copy link
Member

@antonfirsov antonfirsov left a 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?

{
throw WinHttpException.CreateExceptionUsingError(lastError, nameof(Interop.WinHttp.WinHttpQueryHeaders));
if (!(lastError == Interop.WinHttp.ERROR_INSUFFICIENT_BUFFER || lastError == Interop.WinHttp.ERROR_WINHTTP_HEADER_NOT_FOUND))
Copy link
Member

@antonfirsov antonfirsov Jan 6, 2021

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?

Copy link
Member Author

@JamesNK JamesNK Jan 7, 2021

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)

Copy link
Member

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 .

Copy link
Member

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?

Copy link
Member

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.

Copy link
Contributor

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.

Copy link
Member

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.

Copy link
Member Author

@JamesNK JamesNK Jan 13, 2021

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.

Copy link
Member

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?

Copy link
Member Author

@JamesNK JamesNK Jan 28, 2021

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.

@karelz
Copy link
Member

karelz commented Jan 7, 2021

Thanks @JamesNK for your PR!

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 ?)

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.
Key question IMO: Should we treat it as a temporary hack (only some builds of WinHttpHandler expose the property), or as permanent API which we should support on WinHttpHandler in all circumstances? (see #46602 (comment) above)

I think it would be best to have the API discussion on the issue #44778 rather than in the PR ... thoughts?

@JamesNK
Copy link
Member Author

JamesNK commented Jan 19, 2021

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.

Copy link
Member

@antonfirsov antonfirsov left a 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.

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);
Copy link
Member

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?

Copy link
Member Author

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))
Copy link
Member

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?

@antonfirsov
Copy link
Member

/azp run runtime

@antonfirsov antonfirsov added the NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) label Feb 19, 2021
@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@antonfirsov antonfirsov added NO-REVIEW Experimental/testing PR, do NOT review it and removed area-System.Net.Http labels Feb 19, 2021
@antonfirsov
Copy link
Member

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.

@ghost ghost locked as resolved and limited conversation to collaborators Mar 25, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) NO-REVIEW Experimental/testing PR, do NOT review it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

WinHttpHandler HTTP/2 trailing headers
6 participants