public const uint WINHTTP_OPTION_TCP_KEEPALIVE = 152;
public const uint WINHTTP_OPTION_STREAM_ERROR_CODE = 159;
+ public const uint WINHTTP_OPTION_REQUIRE_STREAM_END = 160;
public enum WINHTTP_WEB_SOCKET_BUFFER_TYPE
{
SetSessionHandleTimeoutOptions(sessionHandle);
SetDisableHttp2StreamQueue(sessionHandle);
SetTcpKeepalive(sessionHandle);
+ SetRequireStreamEnd(sessionHandle);
}
private unsafe void SetTcpKeepalive(SafeWinHttpHandle sessionHandle)
}
}
+ private void SetRequireStreamEnd(SafeWinHttpHandle sessionHandle)
+ {
+ if (WinHttpTrailersHelper.OsSupportsTrailers)
+ {
+ // Setting WINHTTP_OPTION_REQUIRE_STREAM_END to TRUE is needed for WinHttp to read trailing headers
+ // in case the response has Content-Lenght defined.
+ // According to the WinHttp team, the feature-detection logic in WinHttpTrailersHelper.OsSupportsTrailers
+ // should also indicate the support of WINHTTP_OPTION_REQUIRE_STREAM_END.
+ // WINHTTP_OPTION_REQUIRE_STREAM_END doesn't have effect on HTTP 1.1 requests, therefore it's safe to set it on
+ // the session handle so it is inhereted by all request handles.
+ uint optionData = 1;
+ if (!Interop.WinHttp.WinHttpSetOption(sessionHandle, Interop.WinHttp.WINHTTP_OPTION_REQUIRE_STREAM_END, ref optionData))
+ {
+ if (NetEventSource.Log.IsEnabled())
+ {
+ NetEventSource.Info(this, "Failed to enable WINHTTP_OPTION_REQUIRE_STREAM_END error code: " + Marshal.GetLastWin32Error());
+ }
+ }
+ }
+ }
+
private void SetSessionHandleConnectionOptions(SafeWinHttpHandle sessionHandle)
{
uint optionData = (uint)_maxConnectionsPerServer;
}
}
- [ConditionalFact(nameof(TestsEnabled))]
- public async Task Http2GetAsync_MissingTrailer_TrailingHeadersAccepted()
+ [InlineData(false)]
+ [InlineData(true)]
+ [ConditionalTheory(nameof(TestsEnabled))]
+ public async Task Http2GetAsync_MissingTrailer_TrailingHeadersAccepted(bool responseHasContentLength)
{
using (Http2LoopbackServer server = Http2LoopbackServer.CreateServer())
using (HttpClient client = CreateHttpClient())
int streamId = await connection.ReadRequestHeaderAsync();
// Response header.
- await connection.SendDefaultResponseHeadersAsync(streamId);
+ if (responseHasContentLength)
+ {
+ await connection.SendResponseHeadersAsync(streamId, endStream: false, headers: new[] { new HttpHeaderData("Content-Length", DataBytes.Length.ToString()) });
+ }
+ else
+ {
+ await connection.SendDefaultResponseHeadersAsync(streamId);
+ }
// Response data, missing Trailers.
await connection.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
}
}
- [ConditionalFact(nameof(TestsEnabled))]
- public async Task Http2GetAsyncResponseHeadersReadOption_TrailingHeaders_Available()
+ [InlineData(false)]
+ [InlineData(true)]
+ [ConditionalTheory(nameof(TestsEnabled))]
+ public async Task Http2GetAsyncResponseHeadersReadOption_TrailingHeaders_Available(bool responseHasContentLength)
{
using (Http2LoopbackServer server = Http2LoopbackServer.CreateServer())
using (HttpClient client = CreateHttpClient())
int streamId = await connection.ReadRequestHeaderAsync();
// Response header.
- await connection.SendDefaultResponseHeadersAsync(streamId);
+ if (responseHasContentLength)
+ {
+ await connection.SendResponseHeadersAsync(streamId, endStream: false, headers: new[] { new HttpHeaderData("Content-Length", DataBytes.Length.ToString()) });
+ }
+ else
+ {
+ await connection.SendDefaultResponseHeadersAsync(streamId);
+ }
// Response data, missing Trailers.
await connection.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
}
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))]
- public async Task Http2GetAsync_MissingTrailer_TrailingHeadersAccepted()
+ [InlineData(false)]
+ [InlineData(true)]
+ [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))]
+ public async Task Http2GetAsync_MissingTrailer_TrailingHeadersAccepted(bool responseHasContentLength)
{
using (Http2LoopbackServer server = Http2LoopbackServer.CreateServer())
using (HttpClient client = CreateHttpClient())
int streamId = await connection.ReadRequestHeaderAsync();
// Response header.
- await connection.SendDefaultResponseHeadersAsync(streamId);
+ if (responseHasContentLength)
+ {
+ await connection.SendResponseHeadersAsync(streamId, endStream: false, headers: new[] { new HttpHeaderData("Content-Length", DataBytes.Length.ToString()) });
+ }
+ else
+ {
+ await connection.SendDefaultResponseHeadersAsync(streamId);
+ }
// Response data, missing Trailers.
await connection.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
}
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))]
- public async Task Http2GetAsyncResponseHeadersReadOption_TrailingHeaders_Available()
+ [InlineData(false)]
+ [InlineData(true)]
+ [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))]
+ public async Task Http2GetAsyncResponseHeadersReadOption_TrailingHeaders_Available(bool responseHasContentLength)
{
using (Http2LoopbackServer server = Http2LoopbackServer.CreateServer())
using (HttpClient client = CreateHttpClient())
int streamId = await connection.ReadRequestHeaderAsync();
// Response header.
- await connection.SendDefaultResponseHeadersAsync(streamId);
+ if (responseHasContentLength)
+ {
+ await connection.SendResponseHeadersAsync(streamId, endStream: false, headers: new[] { new HttpHeaderData("Content-Length", DataBytes.Length.ToString()) });
+ }
+ else
+ {
+ await connection.SendDefaultResponseHeadersAsync(streamId);
+ }
// Response data, missing Trailers.
await connection.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));