Skip to content

Commit

Permalink
Sync HttpClient Send (#34948)
Browse files Browse the repository at this point in the history
Introduces sync version of HttpClient.Send and all necessary changes to make it work synchronously (e.g.: HttpContent, HttpMessageHandler and their child classes).

The change works properly only for HTTP1.1, for unsupported scenarios like HTTP2 throws.

Testing the change uses existing tests calling HttpClient.SendAsync and introduces test argument calling the same test twice, once with HttpClient.SendAsync and then with HttpClient.Send.

Resolves #32125
  • Loading branch information
ManickaP authored Jun 24, 2020
1 parent 81d86df commit 313b165
Show file tree
Hide file tree
Showing 68 changed files with 1,917 additions and 880 deletions.
4 changes: 2 additions & 2 deletions src/libraries/Common/tests/System/IO/DelegateStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ public DelegateStream(
_positionSetFunc = positionSetFunc ?? (_ => { throw new NotSupportedException(); });
_positionGetFunc = positionGetFunc ?? (() => { throw new NotSupportedException(); });

_readFunc = readFunc;
_readAsyncFunc = readAsyncFunc ?? ((buffer, offset, count, token) => base.ReadAsync(buffer, offset, count, token));
_readFunc = readFunc ?? ((buffer, offset, count) => readAsyncFunc(buffer, offset, count, default).GetAwaiter().GetResult());

_seekFunc = seekFunc ?? ((_, __) => { throw new NotSupportedException(); });
_setLengthFunc = setLengthFunc ?? (_ => { throw new NotSupportedException(); });

_writeFunc = writeFunc;
_writeAsyncFunc = writeAsyncFunc ?? ((buffer, offset, count, token) => base.WriteAsync(buffer, offset, count, token));
_writeFunc = writeFunc ?? ((buffer, offset, count) => writeAsyncFunc(buffer, offset, count, default).GetAwaiter().GetResult());

_disposeFunc = disposeFunc;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace System.Net.Http.Functional.Tests
Expand All @@ -24,6 +25,11 @@ public ByteAtATimeContent(int length, Task waitToSend, TaskCompletionSource<bool
_millisecondDelayBetweenBytes = millisecondDelayBetweenBytes;
}

#if NETCOREAPP
protected override void SerializeToStream(Stream stream, TransportContext context, CancellationToken cancellationToken) =>
SerializeToStreamAsync(stream, context).GetAwaiter().GetResult();
#endif

protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
await _waitToSend;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,59 @@ private async Task CreateAndValidateRequest(HttpClientHandler handler, Uri url,

public HttpClientHandler_Authentication_Test(ITestOutputHelper output) : base(output) { }

[Theory]
[MemberData(nameof(Authentication_SocketsHttpHandler_TestData))]
public async Task SocketsHttpHandler_Authentication_Succeeds(string authenticateHeader, bool result)
{
await HttpClientHandler_Authentication_Succeeds(authenticateHeader, result);
}

public static IEnumerable<object[]> Authentication_SocketsHttpHandler_TestData()
{
// These test cases successfully authenticate on SocketsHttpHandler but fail on the other handlers.
// These are legal as per the RFC, so authenticating is the expected behavior.
// See https://github.com/dotnet/runtime/issues/25643 for details.
if (!IsWinHttpHandler)
{
// Unauthorized on WinHttpHandler
yield return new object[] {"Basic realm=\"testrealm1\" basic realm=\"testrealm1\"", true};
yield return new object[] {"Basic something digest something", true};
}
yield return new object[] { "Digest realm=\"[email protected]\", qop=\"auth\", algorithm=MD5-sess, nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\", " +
"opaque=\"HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS\", charset=UTF-8, userhash=true", true };
yield return new object[] { "dIgEsT realm=\"[email protected]\", qop=\"auth\", algorithm=MD5-sess, nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\", " +
"opaque=\"HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS\", charset=UTF-8, userhash=true", true };

// These cases fail on WinHttpHandler because of a behavior in WinHttp that causes requests to be duplicated
// when the digest header has certain parameters. See https://github.com/dotnet/runtime/issues/25644 for details.
if (!IsWinHttpHandler)
{
// Timeouts on WinHttpHandler
yield return new object[] { "Digest ", false };
yield return new object[] { "Digest realm=\"testrealm\", nonce=\"testnonce\", algorithm=\"myown\"", false };
}

// These cases fail to authenticate on SocketsHttpHandler, but succeed on the other handlers.
// they are all invalid as per the RFC, so failing is the expected behavior. See https://github.com/dotnet/runtime/issues/25645 for details.
if (!IsWinHttpHandler)
{
// Timeouts on WinHttpHandler
yield return new object[] {"Digest realm=withoutquotes, nonce=withoutquotes", false};
}
yield return new object[] { "Digest realm=\"testrealm\" nonce=\"testnonce\"", false };
yield return new object[] { "Digest realm=\"testrealm1\", nonce=\"testnonce1\" Digest realm=\"testrealm2\", nonce=\"testnonce2\"", false };

// These tests check that the algorithm parameter is treated in case insensitive way.
// WinHTTP only supports plain MD5, so other algorithms are included here.
yield return new object[] { $"Digest realm=\"testrealm\", algorithm=md5-Sess, nonce=\"testnonce\", qop=\"auth\"", true };
if (!IsWinHttpHandler)
{
// Unauthorized on WinHttpHandler
yield return new object[] { $"Digest realm=\"testrealm\", algorithm=sha-256, nonce=\"testnonce\"", true };
yield return new object[] { $"Digest realm=\"testrealm\", algorithm=sha-256-SESS, nonce=\"testnonce\", qop=\"auth\"", true };
}
}

[Theory]
[MemberData(nameof(Authentication_TestData))]
public async Task HttpClientHandler_Authentication_Succeeds(string authenticateHeader, bool result)
Expand Down Expand Up @@ -568,7 +621,7 @@ public async Task Credentials_DomainJoinedServerUsesKerberos_UseIpAddressAndHost
_output.WriteLine(request.RequestUri.AbsoluteUri.ToString());
_output.WriteLine($"Host: {request.Headers.Host}");

using (HttpResponseMessage response = await client.SendAsync(request))
using (HttpResponseMessage response = await client.SendAsync(TestAsync, request))
{
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
string body = await response.Content.ReadAsStringAsync();
Expand Down Expand Up @@ -602,12 +655,11 @@ public async Task Credentials_ServerUsesWindowsAuthentication_Success(string ser
[InlineData("Negotiate")]
public async Task Credentials_ServerChallengesWithWindowsAuth_ClientSendsWindowsAuthHeader(string authScheme)
{
#if WINHTTPHANDLER_TEST
if (UseVersion > HttpVersion.Version11)
if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
{
throw new SkipTestException($"Test doesn't support {UseVersion} protocol.");
return;
}
#endif

await LoopbackServerFactory.CreateClientAndServerAsync(
async uri =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,32 +40,15 @@ public static IEnumerable<object[]> RemoteServersAndRedirectStatusCodes()
}
}

public static readonly object[][] RedirectStatusCodesOldMethodsNewMethods = {
new object[] { 300, "GET", "GET" },
new object[] { 300, "POST", "GET" },
new object[] { 300, "HEAD", "HEAD" },

new object[] { 301, "GET", "GET" },
new object[] { 301, "POST", "GET" },
new object[] { 301, "HEAD", "HEAD" },

new object[] { 302, "GET", "GET" },
new object[] { 302, "POST", "GET" },
new object[] { 302, "HEAD", "HEAD" },

new object[] { 303, "GET", "GET" },
new object[] { 303, "POST", "GET" },
new object[] { 303, "HEAD", "HEAD" },

new object[] { 307, "GET", "GET" },
new object[] { 307, "POST", "POST" },
new object[] { 307, "HEAD", "HEAD" },

new object[] { 308, "GET", "GET" },
new object[] { 308, "POST", "POST" },
new object[] { 308, "HEAD", "HEAD" },
};

public static IEnumerable<object[]> RedirectStatusCodesOldMethodsNewMethods()
{
foreach (int statusCode in new[] { 300, 301, 302, 303, 307, 308 })
{
yield return new object[] { statusCode, "GET", "GET" };
yield return new object[] { statusCode, "POST", statusCode <= 303 ? "GET" : "POST" };
yield return new object[] { statusCode, "HEAD", "HEAD" };
}
}
public HttpClientHandlerTest_AutoRedirect(ITestOutputHelper output) : base(output) { }

[OuterLoop("Uses external server")]
Expand Down Expand Up @@ -112,7 +95,7 @@ await LoopbackServer.CreateServerAsync(async (origServer, origUrl) =>
{
var request = new HttpRequestMessage(new HttpMethod(oldMethod), origUrl) { Version = UseVersion };
Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
Task<HttpResponseMessage> getResponseTask = client.SendAsync(TestAsync, request);
await LoopbackServer.CreateServerAsync(async (redirServer, redirUrl) =>
{
Expand Down Expand Up @@ -157,7 +140,7 @@ await LoopbackServer.CreateServerAsync(async (origServer, origUrl) =>
request.Content = new StringContent(ExpectedContent);
request.Headers.TransferEncodingChunked = true;
Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
Task<HttpResponseMessage> getResponseTask = client.SendAsync(TestAsync, request);
await LoopbackServer.CreateServerAsync(async (redirServer, redirUrl) =>
{
Expand Down
Loading

0 comments on commit 313b165

Please sign in to comment.