-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Provide sync API on HttpClient, which currently has only async methods.
Motivation
Azure SDK currently exposes synchronous API which does sync-over-async on HttpClient (see HttpClientTransport.cs). It would be much better if we provided them with sync API with proper sync implementation, where feasible.
Also there are lots of existing code bases that have very deep synchronous call stacks. Developers are simply not willing to rewrite these code bases to be asynchronous. If they need to call async only API in these synchronous methods, they use sync-over-async, which then in turn causes "soft" deadlock. We want to provide synchronous APIs for these developers because synchronous APIs, although inefficient, can can help in avoiding these soft deadlocks.
Another advantage of sync API is that it is much easier to grasp. Especially for people with no prior knowledge of asynchronous processing. If someone is starting with HttpClient, they also have to be knowledgeable of C# async/await. Also many examples with ``HttpClient` might be simplified and thus making the entry into .NET easier.
Proposed API
Minimal Necessary Change
This change is based on @stephentoub prototype in stephentoub/corefx@0e4d640. The prototype introduces synchronous API for SocketsHttpHandler and also properly implements it for HTTP 1.1 scenarios (for HTTP 2 does sync-over-async). This proposal extends the existing prototype with sync API on HttpClient. Following changes are a minimal set to achieve synchronous HttpClient.Send behaving trully synchronously at least for HTTP 1.1.
Note that CancellationToken is used in synchronous methods in order to propagate HttpClient timeout and cancellations. For HttpContent it's up for discussion whether to add other overload to CopyTo and SerializeToStream to match the async counterparts.
The prototype/early implementation for the minimal change can be found in my branch https://github.com/ManickaP/runtime/tree/mapichov/sync_http_api.
// The main classes where we want the sync API.
public partial class HttpMessageInvoker : IDisposable
{
...
// Existing async method
public virtual Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
// Proposed new sync methods
public virtual HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken);
}
public partial class HttpClient : HttpMessageInvoker
{
...
// Existing async methods
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption);
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken);
public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
// Proposed new sync methods
public HttpResponseMessage Send(HttpRequestMessage request, HttpCompletionOption completionOption = default, CancellationToken cancellationToken = default);
public override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken);
}
// The changes bellow enable proper implementation of the sync API.
// HttpMessageHandler and derived classes
public abstract partial class HttpMessageHandler : IDisposable
{
...
// Existing async method
protected internal abstract Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
// Proposed new sync method
protected internal virtual HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken);
}
public abstract partial class DelegatingHandler : HttpMessageHandler
{
...
// Proposed new sync method
protected internal override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken);
}
public abstract partial class HttpClientHandler : HttpMessageHandler
{
...
// Proposed new sync method
protected internal override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken);
}
public abstract partial class SocketsHttpHandler : HttpMessageHandler
{
...
// Proposed new sync method
protected internal override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken);
}
public abstract partial class MessageProcessingHandler : DelegatingHandler
{
...
// Proposed new sync method
protected internal sealed override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken);
}
// HttpContent and derived classes
public abstract partial class HttpContent : IDisposable
{
...
// Existing async methods
public Task CopyToAsync(Stream stream);
public Task CopyToAsync(Stream stream, TransportContext context);
public Task CopyToAsync(Stream stream, TransportContext context, CancellationToken cancellationToken);
public Task CopyToAsync(Stream stream, CancellationToken cancellationToken);
protected abstract Task SerializeToStreamAsync(Stream stream, TransportContext context);
protected virtual Task SerializeToStreamAsync(Stream stream, TransportContext context, CancellationToken cancellationToken);
// Proposed new sync methods
public void CopyTo(Stream stream, TransportContext context, CancellationToken cancellationToken);
protected virtual void SerializeToStream(Stream stream, TransportContext context, CancellationToken cancellationToken);
}
public partial class ByteArrayContent : HttpContent
{
...
// Proposed new sync method
protected override void SerializeToStream(Stream stream, TransportContext context, CancellationToken cancellationToken);
}
public partial class MultipartContent : HttpContent, IEnumerable<HttpContent>, IEnumerable
{
...
// Proposed new sync method
protected override void SerializeToStream(Stream stream, TransportContext context, CancellationToken cancellationToken);
}
public sealed partial class ReadOnlyMemoryContent : HttpContent
{
...
// Proposed new sync method
protected override void SerializeToStream(Stream stream, TransportContext context, CancellationToken cancellationToken);
}
public partial class StreamContent : HttpContent
{
...
// Proposed new sync method
protected override void SerializeToStream(Stream stream, TransportContext context, CancellationToken cancellationToken);
}Full Change
This includes sync counterparts to all async HttpClient methods. Since their implementation delegates to Send underneath, there's no need to add any other supporting sync methods.
Note that all the sync methods on HttpClient (except for HttpMessageInvoker.Send override) do not need oveloads with CancellationToken. Whether to include them or not is up for discussion.
public partial class HttpClient : HttpMessageInvoker
{
...
// Existing async methods
public Task<HttpResponseMessage> DeleteAsync(string requestUri);
public Task<HttpResponseMessage> DeleteAsync(string requestUri, CancellationToken cancellationToken);
public Task<HttpResponseMessage> DeleteAsync(Uri requestUri);
public Task<HttpResponseMessage> DeleteAsync(Uri requestUri, CancellationToken cancellationToken);
public Task<HttpResponseMessage> GetAsync(string requestUri);
public Task<HttpResponseMessage> GetAsync(string requestUri, HttpCompletionOption completionOption);
public Task<HttpResponseMessage> GetAsync(string requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken);
public Task<HttpResponseMessage> GetAsync(string requestUri, CancellationToken cancellationToken);
public Task<HttpResponseMessage> GetAsync(Uri requestUri);
public Task<HttpResponseMessage> GetAsync(Uri requestUri, HttpCompletionOption completionOption);
public Task<HttpResponseMessage> GetAsync(Uri requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken);
public Task<HttpResponseMessage> GetAsync(Uri requestUri, CancellationToken cancellationToken);
public Task<byte[]> GetByteArrayAsync(string requestUri);
public Task<byte[]> GetByteArrayAsync(string requestUri, CancellationToken cancellationToken);
public Task<byte[]> GetByteArrayAsync(Uri requestUri);
public Task<byte[]> GetByteArrayAsync(Uri requestUri, CancellationToken cancellationToken);
public Task<Stream> GetStreamAsync(string requestUri);
public Task<Stream> GetStreamAsync(string requestUri, CancellationToken cancellationToken);
public Task<Stream> GetStreamAsync(Uri requestUri);
public Task<Stream> GetStreamAsync(Uri requestUri, CancellationToken cancellationToken);
public Task<string> GetStringAsync(string requestUri);
public Task<string> GetStringAsync(string requestUri, CancellationToken cancellationToken);
public Task<string> GetStringAsync(Uri requestUri);
public Task<string> GetStringAsync(Uri requestUri, CancellationToken cancellationToken);
public Task<HttpResponseMessage> PatchAsync(string requestUri, HttpContent content);
public Task<HttpResponseMessage> PatchAsync(string requestUri, HttpContent content, CancellationToken cancellationToken);
public Task<HttpResponseMessage> PatchAsync(Uri requestUri, HttpContent content);
public Task<HttpResponseMessage> PatchAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken);
public Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content);
public Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content, CancellationToken cancellationToken);
public Task<HttpResponseMessage> PostAsync(Uri requestUri, HttpContent content);
public Task<HttpResponseMessage> PostAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken);
public Task<HttpResponseMessage> PutAsync(string requestUri, HttpContent content);
public Task<HttpResponseMessage> PutAsync(string requestUri, HttpContent content, CancellationToken cancellationToken);
public Task<HttpResponseMessage> PutAsync(Uri requestUri, HttpContent content);
public Task<HttpResponseMessage> PutAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken);
// Proposed new sync methods
public HttpResponseMessage Delete(string requestUri);
public HttpResponseMessage Delete(string requestUri, CancellationToken cancellationToken);
public HttpResponseMessage Delete(Uri requestUri);
public HttpResponseMessage Delete(Uri requestUri, CancellationToken cancellationToken);
public HttpResponseMessage Get(string requestUri);
public HttpResponseMessage Get(string requestUri, HttpCompletionOption completionOption);
public HttpResponseMessage Get(string requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken);
public HttpResponseMessage Get(string requestUri, CancellationToken cancellationToken);
public HttpResponseMessage Get(Uri requestUri);
public HttpResponseMessage Get(Uri requestUri, HttpCompletionOption completionOption);
public HttpResponseMessage Get(Uri requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken);
public HttpResponseMessage Get(Uri requestUri, CancellationToken cancellationToken);
public byte[] GetByteArray(string requestUri);
public byte[] GetByteArray(string requestUri, CancellationToken cancellationToken);
public byte[] GetByteArray(Uri requestUri);
public byte[] GetByteArray(Uri requestUri, CancellationToken cancellationToken);
public Stream GetStream(string requestUri);
public Stream GetStream(string requestUri, CancellationToken cancellationToken);
public Stream GetStream(Uri requestUri);
public Stream GetStream(Uri requestUri, CancellationToken cancellationToken);
public string GetString(string requestUri);
public string GetString(string requestUri, CancellationToken cancellationToken);
public string GetString(Uri requestUri);
public string GetString(Uri requestUri, CancellationToken cancellationToken);
public HttpResponseMessage Patch(string requestUri, HttpContent content);
public HttpResponseMessage Patch(string requestUri, HttpContent content, CancellationToken cancellationToken);
public HttpResponseMessage Patch(Uri requestUri, HttpContent content);
public HttpResponseMessage Patch(Uri requestUri, HttpContent content, CancellationToken cancellationToken);
public HttpResponseMessage Post(string requestUri, HttpContent content);
public HttpResponseMessage Post(string requestUri, HttpContent content, CancellationToken cancellationToken);
public HttpResponseMessage Post(Uri requestUri, HttpContent content);
public HttpResponseMessage Post(Uri requestUri, HttpContent content, CancellationToken cancellationToken);
public HttpResponseMessage Put(string requestUri, HttpContent content);
public HttpResponseMessage Put(string requestUri, HttpContent content, CancellationToken cancellationToken);
public HttpResponseMessage Put(Uri requestUri, HttpContent content);
public HttpResponseMessage Put(Uri requestUri, HttpContent content, CancellationToken cancellationToken);
}@stephentoub @KrzysztofCwalina @dotnet/ncl