理解ASP.NET Core - 发送Http请求(HttpClient)
注:本文隶属于《理解ASP.NET Core》系列文章,请查看置顶博客或点击此处查看全文目录
前言
在.NET中,我们有很多发送Http请求的手段,如HttpWebRequest、WebClient以及HttpClient。
在进入正文之前,先简单了解一下前2个:
HttpWebRequest
namespace System.Net
{
public class HttpWebRequest : WebRequest, ISerializable { }
}
HttpWebRequest位于System.Net命名空间下,继承自抽象类WebRequest,是.NET中最早、最原始地用于操作Http请求的类。相对来说,该类提供的方法更接近于底层,所以它的使用较为繁琐,对于开发者的水平要求是比较高的。
WebClient
namespace System.Net
{
public class WebClient : Component { }
}
同样的,WebClient也位于System.Net命名空间下,它主要是对WebRequest进行了一层封装,简化了常用任务场景的使用,如文件上传、文件下载、数据上传、数据下载等,并提供了一系列事件。
不过,虽然HttpWebRequest和WebClient仍然可用,但官方建议,若没有特殊要求,不要使用他俩,而应该使用HttpClient。那HttpClient是什么呢?
HttpClient
namespace System.Net.Http
{
public class HttpClient : HttpMessageInvoker { }
}
HttpClient位于System.Net.Http命名空间下,它提供了GetAsync、PostAsync、PutAsync、DeleteAsync、PatchAsync等方法,更适合操作当下流行的Rest风格的Http Api。而且,它提供的方法几乎都是异步的,非常适合当下的异步编程模型。
而且,HttpClient旨在实例化一次,并在应用程序的整个生命周期内重复使用,也就是说,可以使用一个HttpClient实例可以发送多次以及多个不同的请求。
不过需要注意的是,如果每次请求反而都实例化一个HttpClient,由于Dispose并不会立即释放套接字,那么当短时间内有大量请求时,就会导致服务器的套接字数被耗尽,从而引发SocketException异常。
我们一起来看一个错误的示例:
public class ValuesController : ControllerBase
{
[HttpGet("WrongUsage")]
public async Task<string> WrongUsage()
{
try
{
// 模拟10次请求,每次请求都创建一个新的 HttpClient
var i = 0;
while (i++ < 10)
{
using var client = new HttpClient();
await client.GetAsync("https://jsonplaceholder.typicode.com/posts/1");
}
return "Success";
}
catch (Exception ex)
{
return ex.ToString();
}
}
}
jsonplaceholder.typicode.com 是一个免费提供虚假API的网站,我们可以使用它来方便测试。
在Windows中,当你请求WrongUsage接口之后,可以通过 netstat 命令查看套接字连接(jsonplaceholder的IP为172.67.131.170:443),你会发现程序虽然已经退出了,但是连接并没有像我们所预期的那样立即关闭:
> netstat -n | find "172.67.131.170"
TCP 172.16.161.10:1057 172.67.131.170:443 TIME_WAIT
TCP 172.16.161.10:1058 172.67.131.170:443 TIME_WAIT
TCP 172.16.161.10:1061 172.67.131.170:443 TIME_WAIT
TCP 172.16.161.10:1065 172.67.131.170:443 TIME_WAIT
TCP 172.16.161.10:1070 172.67.131.170:443 TIME_WAIT
TCP 172.16.161.10:1073 172.67.131.170:443 TIME_WAIT
TCP 172.16.161.10:10005 172.67.131.170:443 TIME_WAIT
下面是一个较为合理的示例:
public class ValuesController : ControllerBase
{
private static readonly HttpClient _httpClient;
static ValuesController()
{
// 复用同一个实例
_httpClient = new HttpClient();
}
}
可以看出,HttpClient很容易被错误使用,并且,即使是上面的正确示例,仍然有很多待优化的地方。因此,为了解决这个问题,IHttpClientFactory诞生了。
IHttpClientFactory
看名字就知道了,IHttpClientFactory可以帮我们创建所需要的HttpClient实例,我们无须关心实例的创建过程。与HttpClient一样,位于System.Net.Http命名空间下。
下面先了解一下它的一些用法。
基础用法
首先,注册HttpClientFactory相关的服务
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient();
然后,构造函数注入IHttpClientFactory,通过CreateClient()创建Client实例。
public class ValuesController : ControllerBase
{
private readonly IHttpClientFactory _httpClientFactory;
public ValuesController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
[HttpGet]
public async Task<string> Get()
{
// 通过 _httpClientFactory 创建 Client 实例
var client = _httpClientFactory.CreateClient();
var response = await client.GetAsync("https://jsonplaceholder.typicode.com/posts/1");
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
return $"{response.StatusCode}: {response.ReasonPhrase}";
}
}
输出:
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
命名客户端
类似于命名选项,我们也可以添加命名的HttpClient,并添加一些全局默认配置。下面我们添加一个名为jsonplaceholder的客户端:
// jsonplaceholder client
builder.Services.AddHttpClient("jsonplaceholder", (sp, client) =>
{
// 基址
client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
// 请求头
client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
client.DefaultRequestHeaders.Add(HeaderNames.UserAgent, "HttpClientFactory-Sample-Named");
});
[HttpGet("named")]
public async Task<dynamic> GetNamed()
{
// 获取指定名称的 Client
var client = _httpClientFactory.CreateClient("jsonplaceholder");
var response = await client.GetAsync("posts/1");
if (response.IsSuccessStatusCode)
{
return new
{
Content = await response.Content.ReadAsStringAsync(),
AcceptHeader = response.RequestMessage!.Headers.Accept.ToString(),
UserAgentHeader = response.RequestMessage.Headers.UserAgent.ToString()
};
}
return $"{response.StatusCode}: {response.ReasonPhrase}";
}
输出:
{
"content": "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"sunt aut facere repellat provident occaecati excepturi optio reprehenderit\",\n \"body\": \"quia et suscipit\\nsuscipit recusandae consequuntur expedita et cum\\nreprehenderit molestiae ut ut quas totam\\nnostrum rerum est autem sunt rem eveniet architecto\"\n}",
"acceptHeader": "application/json",
"userAgentHeader": "HttpClientFactory-Sample-Named"
}
实际上,在创建HttpClient实例时,也可以指定未在服务中注册的HttpClient名字。读完文章后面,你就知道为什么了。
类型化客户端
客户端也可以被类型化,这样做的好处有:
- 无需像命名客户端那样通过传递字符串获取客户端实例
- 可以将同一类别的调用接口进行归类、封装
- 有智能提示
下面看个简单地例子,首先,创建一个类型化客户端JsonPlaceholderClient,用于封装对jsonplaceholder接口的调用:
public class JsonPlaceholderClient
{
private readonly HttpClient _httpClient;
// 直接注入 HttpClient
public JsonPlaceholderClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<dynamic> GetPost(int id) =>
await _httpClient.GetFromJsonAsync<dynamic>($"/posts/{id}");
}
为了让DI容器知道要将哪个HttpClient实例注入到JsonPlaceholderClient的构造函数,我们需要配置一下服务:
builder.Services.AddHttpClient<JsonPlaceholderClient>(client =>
{
client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
client.DefaultRequestHeaders.Add(HeaderNames.UserAgent, "HttpClientFactory-Sample-Typed");
});
最后,我们直接注入JsonPlaceholderClient,而不再是IHttpClientFactory,使用起来就好像在调用本地服务似的:
public class ValuesController : ControllerBase
{
private readonly JsonPlaceholderClient _jsonPlaceholderClient;
public ValuesController(JsonPlaceholderClient jsonPlaceholderClient)
{
_jsonPlaceholderClient = jsonPlaceholderClient;
}
[HttpGet("typed")]
public async Task<dynamic> GetTyped()
{
var post = await _jsonPlaceholderClient.GetPost(1);
return post;
}
}
借助第三方库生成的客户端
一般来说,类型化的客户端已经大大简化了我们使用HttpClient的步骤和难度,不过,我们还可以借助第三方库再次简化我们的代码:我们只需要定义要调用的服务接口,第三方库会生成代理类。
常用的第三方库有以下两个:
这两个第三方库的使用方式非常类似,由于我比较熟悉WebApiClientCore,所以后面的示例均使用它进行演示。
首先,安装Nuget包:
Install-Package WebApiClientCore
接着,创建一个接口IJsonPlaceholderApi:
[Header("User-Agent", "HttpClientFactory-Sample-Api")]
[Header("Custom-Header", "Custom-Value")]
public interface IJsonPlaceholderApi
{
[HttpGet("/posts/{id}")]
Task<dynamic> GetPost(int id);
}
怎么样,看起来是不是很像在写Web Api?
对了,别忘了进行服务注册:
builder.Services.AddHttpApi<IJsonPlaceholderApi>(
o =>
{
o.HttpHost = new Uri("https://jsonplaceholder.typicode.com/");
o.UseDefaultUserAgent = false;
});
最后,我们就可以更方便地用它了:
public class ValuesController : ControllerBase
{
private readonly IJsonPlaceholderApi _jsonPlaceholderApi;
public ValuesController(IJsonPlaceholderApi jsonPlaceholderApi)
{
_jsonPlaceholderApi = jsonPlaceholderApi;
}
[HttpGet("api")]
public async Task<dynamic> GetApi()
{
var post = await _jsonPlaceholderApi.GetPost(1);
return post;
}
}
HttpClient设计原理
上面我们提到过:HttpClient旨在实例化一次,并在应用程序的整个生命周期内重复使用。如果每次请求都实例化一个HttpClient,由于Dispose并不会立即释放套接字,那么当短时间内有大量请求时,服务器的套接字数就会被耗尽,从而引发SocketException异常。
为了能够真正理解这句话,我们一起看一下HttpClient的是如何发送请求并处理响应结果的。
下面,我们先看下HttpClient的基本结构:
按照惯例,为了方便理解,后续列出的源码中我已经删除了一些不是那么重要的代码。
public class HttpMessageInvoker : IDisposable
{
private volatile bool _disposed;
private readonly bool _disposeHandler;
private readonly HttpMessageHandler _handler;
public HttpMessageInvoker(HttpMessageHandler handler) : this(handler, true) { }
public HttpMessageInvoker(HttpMessageHandler handler, bool disposeHandler)
{
_handler = handler;
_disposeHandler = disposeHandler;
}
[UnsupportedOSPlatformAttribute("browser")]
public virtual HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken) =>
_handler.Send(request, cancellationToken);
public virtual Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) =>
_handler.SendAsync(request, cancellationToken);
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing && !_disposed)
{
_disposed = true;
if (_disposeHandler)
{
_handler.Dispose();
}
}
}
}
public class HttpClient : HttpMessageInvoker
{
private const HttpCompletionOption DefaultCompletionOption = HttpCompletionOption.ResponseContentRead;
private volatile bool _disposed;
private int _maxResponseContentBufferSize;
public HttpClient() : this(new HttpClientHandler()) { }
public HttpClient(HttpMessageHandler handler) : this(handler, true) { }
public HttpClient(HttpMessageHandler handler, bool disposeHandler) : base(handler, disposeHandler) =>
_maxResponseContentBufferSize = HttpContent.MaxBufferSize;
// 中间的Rest方法就略过了,因为它们的内部都是通过调用 SendAsync 实现的
// 同步的 Send 方法与异步的 SendAsync 实现类似
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request) =>
SendAsync(request, DefaultCompletionOption, CancellationToken.None);
public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) =>
SendAsync(request, DefaultCompletionOption, cancellationToken);
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption) =>
SendAsync(request, completionOption, CancellationToken.None);
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cts.Token).ConfigureAwait(false);
ThrowForNullResponse(response);
if (ShouldBufferResponse(completionOption, request))
{
await response.Content.LoadIntoBufferAsync(_maxResponseContentBufferSize, cts.Token).ConfigureAwait(false);
}
return response;
}
private static void ThrowForNullResponse(HttpResponseMessage? response)
{
if (response is null) throw new InvalidOperationException(...);
}
private static bool ShouldBufferResponse(HttpCompletionOption completionOption, HttpRequestMessage request) =>
completionOption == HttpCompletionOption.ResponseContentRead
&& !string.Equals(request.Method.Method, "HEAD", StringComparison.OrdinalIgnoreCase);
protected override void Dispose(bool disposing)
{
if (disposing && !_disposed)
{
_disposed = true;
// ...
}
base.Dispose(disposing);
}
}
看过之后,我们对HttpClient的基本结构可以有一个清晰的认识:
HttpClient继承自HttpMessageInvoker,“调用者”,很形象的一个名字。Send/SendAsync方法是整个类的核心方法,所有的请求都是通过调用它们来实现的HttpClient只是对HttpMessageHandler的包装,实际上,所有的请求都是通过这个Handler来发送的。
如果你足够细心,你会发现其中的一个构造函数接收了一个名为disposeHandler的参数,用于指示是否要释放HttpMessageHandler。为什么要这么设计呢?我们知道,HttpClient旨在实例化一次,并在应用程序的整个生命周期内重复使用,实际上指的是HttpMessageHandler,为了在多个地方复用它,该参数允许我们创建多个HttpClient实例,但使用的都是同一个HttpMessageHandler实例(参见下方的IHttpClientFactory设计方式)。
下面看一下HttpMessageHandler及其子类HttpClientHandler:
public abstract class HttpMessageHandler : IDisposable
{
protected HttpMessageHandler() { }
// 这个方法是后加的,为了不影响它的已存在的子类,所以将其设置为了virtual(而不是abstract),并默认抛NSE
protected internal virtual HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
{
throw new NotSupportedException(...);
}
protected internal abstract Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
protected virtual void Dispose(bool disposing)
{
// 基类中啥都没干
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
// 这里我们不讨论作为WASM运行在浏览器中的情况
public class HttpClientHandler : HttpMessageHandler
{
// Socket
private readonly SocketsHttpHandler _underlyingHandler;
private volatile bool _disposed;
public HttpClientHandler()
{
_underlyingHandler = new HttpHandlerType();
ClientCertificateOptions = ClientCertificateOption.Manual;
}
private HttpMessageHandler Handler => _underlyingHandler;
// Send 与 SendAsync 类似
protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) =>
Handler.SendAsync(request, cancellationToken);
protected override void Dispose(bool disposing)
{
if (disposing && !_disposed)
{
_disposed = true;
_underlyingHandler.Dispose();
}
base.Dispose(disposing);
}
}
实际上,在.NET Core 2.1(不包含)之前,HttpClient默认使用的HttpMessageHandler在各个平台上的实现各不相同,直到.NET Core 2.1开始,HttpClient才统一默认使用SocketsHttpHandler,这带来了很多好处:
- 更高的性能
- 消除了平台依赖,简化了部署和服务
- 在所有的.NET平台上行为一致
[UnsupportedOSPlatform("browser")]
public sealed class SocketsHttpHandler : HttpMessageHandler
{
private readonly HttpConnectionSettings _settings = new HttpConnectionSettings();
private HttpMessageHandlerStage? _handler;
private bool _disposed;
// Send 与 SendAsync 类似
protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpMessageHandler handler = _handler ?? SetupHandlerChain();
return handler.SendAsync(request, cancellationToken);
}
private HttpMessageHandlerStage SetupHandlerChain()
{
HttpConnectionSettings settings = _settings.CloneAndNormalize();
HttpConnectionPoolManager poolManager = new HttpConnectionPoolManager(settings);
HttpMessageHandlerStage handler;
if (settings._credentials == null)
{
handler = new HttpConnectionHandler(poolManager);
}
else
{
handler = new HttpAuthenticatedConnectionHandler(poolManager);
}
// 省略了一些Handlers管道的组装,与中间件管道类似
// 释放旧的 _handler
if (Interlocked.CompareExchange(ref _handler, handler, null) != null)
{
handler.Dispose();
}
return _handler;
}
protected override void Dispose(bool disposing)
{
if (disposing && !_disposed)
{
_disposed = true;
_handler?.Dispose();
}
base.Dispose(disposing);
}
}
// HttpAuthenticatedConnectionHandler 结构类似
internal sealed class HttpConnectionHandler : HttpMessageHandlerStage
{
// Http连接池管理器
private readonly HttpConnectionPoolManager _poolManager;
public HttpConnectionHandler(HttpConnectionPoolManager poolManager) =>
_poolManager = poolManager;
internal override ValueTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool async, CancellationToken cancellationToken) =>
_poolManager.SendAsync(request, async, doRequestAuth: false, cancellationToken);
protected override void Dispose(bool disposing)
{
if (disposing)
{
_poolManager.Dispose();
}
base.Dispose(disposing);
}
}
后面的就比较底层了,今天咱们就看到这里吧。下面我们看一下IHttpClientFactory。
IHttpClientFactory设计方式
我们先从服务注册看起:
public static class HttpClientFactoryServiceCollectionExtensions
{
public static IServiceCollection AddHttpClient(this IServiceCollection services)
{
services.AddLogging();
services.AddOptions();
// 核心服务
services.TryAddTransient<HttpMessageHandlerBuilder, DefaultHttpMessageHandlerBuilder>();
services.TryAddSingleton<DefaultHttpClientFactory>();
services.TryAddSingleton<IHttpClientFactory>(serviceProvider => serviceProvider.GetRequiredService<DefaultHttpClientFactory>());
services.TryAddSingleton<IHttpMessageHandlerFactory>(serviceProvider => serviceProvider.GetRequiredService<DefaultHttpClientFactory>());
// 类型化客户端服务
services.TryAdd(ServiceDescriptor.Transient(typeof(ITypedHttpClientFactory<>), typeof(DefaultTypedHttpClientFactory<>)));
services.TryAdd(ServiceDescriptor.Singleton(typeof(DefaultTypedHttpClientFactory<>.Cache), typeof(DefaultTypedHttpClientFactory<>.Cache)));
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHttpMessageHandlerBuilderFilter, LoggingHttpMessageHandlerBuilderFilter>());
services.TryAddSingleton(new HttpClientMappingRegistry());
// 默认注册一个名字为空字符串的 HttpClient 实例
services.TryAddTransient(s => s.GetRequiredService<IHttpClientFactory>().CreateClient(string.Empty));
return services;
}
public static IHttpClientBuilder AddHttpClient(this IServiceCollection services, string name)
{
AddHttpClient(services);
// 返回一个Builder,以允许继续针对HttpClient进行配置
return new DefaultHttpClientBuilder(services, name);
}
public static IHttpClientBuilder AddHttpClient<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TClient>(
this IServiceCollection services)
where TClient : class
{
AddHttpClient(services);
// 获取类型名作为客户端名
string name = TypeNameHelper.GetTypeDisplayName(typeof(TClient), fullName: false);
var builder = new DefaultHttpClientBuilder(services, name);
// 目的是通过 ActivatorUtilities 动态创建 TClient 实例,并通过构造函数注入 HttpClient
builder.AddTypedClientCore<TClient>(validateSingleType: true);
return builder;
}
}
很显然,HttpMessageHandlerBuilder的作用就是创建HttpMessageHandler实例,默认实现为DefaultHttpMessageHandlerBuilder
IHttpMessageHandlerBuilderFilter会在DefaultHttpClientFactory中用到,它可以在HttpMessageHandlerBuilder.Build调用之前对HttpMessageHandlerBuilder进行一些初始化操作。
IHttpClientFactory接口的默认实现是DefaultHttpClientFactory:
internal class DefaultHttpClientFactory : IHttpClientFactory, IHttpMessageHandlerFactory
{
private readonly IServiceProvider _services;
private readonly Func<string, Lazy<ActiveHandlerTrackingEntry>> _entryFactory;
// 有效的Handler对象池,使用Lazy来保证每个命名客户端具有唯一的 HttpMessageHandler 实例
internal readonly ConcurrentDictionary<string, Lazy<ActiveHandlerTrackingEntry>> _activeHandlers;
// 过期的Handler集合
internal readonly ConcurrentQueue<ExpiredHandlerTrackingEntry> _expiredHandlers;
public DefaultHttpClientFactory(
IServiceProvider services,
IServiceScopeFactory scopeFactory,
ILoggerFactory loggerFactory,
IOptionsMonitor<HttpClientFactoryOptions> optionsMonitor,
IEnumerable<IHttpMessageHandlerBuilderFilter> filters)
{
_services = services;
_activeHandlers = new ConcurrentDictionary<string, Lazy<ActiveHandlerTrackingEntry>>(StringComparer.Ordinal);
_entryFactory = (name) =>
{
return new Lazy<ActiveHandlerTrackingEntry>(() =>
{
return CreateHandlerEntry(name);
}, LazyThreadSafetyMode.ExecutionAndPublication);
};
}
public HttpClient CreateClient(string name)
{
HttpMessageHandler handler = CreateHandler(name);
return new HttpClient(handler, disposeHandler: false);
}
public HttpMessageHandler CreateHandler(string name)
{
// 若存在指定的命名客户端的活跃的Handler,则直接使用,若不存在,则新建一个
ActiveHandlerTrackingEntry entry = _activeHandlers.GetOrAdd(name, _entryFactory).Value;
return entry.Handler;
}
internal ActiveHandlerTrackingEntry CreateHandlerEntry(string name)
{
HttpMessageHandlerBuilder builder = _services.GetRequiredService<HttpMessageHandlerBuilder>();
builder.Name = name;
var handler = new LifetimeTrackingHttpMessageHandler(builder.Build());
// options.HandlerLifetime 默认2分钟
return new ActiveHandlerTrackingEntry(name, handler, scope, options.HandlerLifetime);
}
}
public static class HttpClientFactoryExtensions
{
public static HttpClient CreateClient(this IHttpClientFactory factory) =>
factory.CreateClient(Options.DefaultName); // 名字为 string.Empty
}
可以发现,我们每次调用CreateClient,都是新创建一个HttpClient实例,但是,当这些HttpClient实例同名时,所使用的HttpMessageHandler在一定条件下,其实都是同一个。
另外,你也可以发现,所有通过IHttpClientFactory创建的HttpClient,都是命名客户端:
- 未指定名字的,则默认使用空字符串作为客户端的名字
- 类型客户端使用类型名作为客户端的名字
Handler的创建是通过DefaultHttpMessageHandlerBuilder调用Build来实现的,不同的是,Factory并非是简单地创建一个Handler,而是建立了一个Handler管道,这是通过抽象类DelegatingHandler实现的。其中,管道最底层的Handler默认是HttpClientHandler,与我们直接new HttpClient()时所创建的Handler是一样的。
与中间件管道类似,DelegatingHandler的作用就是将Http请求的发送和处理委托给内部的另一个Handler处理,而它可以在这个Handler处理之前和之后加一些自己的特定逻辑。
public abstract class DelegatingHandler : HttpMessageHandler
{
private HttpMessageHandler? _innerHandler;
private volatile bool _disposed;
[DisallowNull]
public HttpMessageHandler? InnerHandler
{
get => _innerHandler;
set => _innerHandler = value;
}
protected DelegatingHandler() { }
// 这里接收的innerHandler就是负责发送和处理Http请求的
protected DelegatingHandler(HttpMessageHandler innerHandler) =>
InnerHandler = innerHandler;
protected internal override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken) =>
_innerHandler!.Send(request, cancellationToken);
protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) =>
_innerHandler!.SendAsync(request, cancellationToken);
protected override void Dispose(bool disposing)
{
if (disposing && !_disposed)
{
_disposed = true;
if (_innerHandler != null)
{
_innerHandler.Dispose();
}
}
base.Dispose(disposing);
}
}
这里我们看到的LifetimeTrackingHttpMessageHandler,以及源码中我删除掉的LoggingHttpMessageHandler都是DelegatingHandler的子类。
你有没有想过,为啥最后要包装成LifetimeTrackingHttpMessageHandler呢?其实很简单,它就是一个标识,标志着它内部的Handler在超出生命周期后,需要被释放。
另外,实际上,创建好的HttpMessageHandler并非能够一直重用,默认可重用的生命周期为2分钟,我们会将可重用的放在_activeHandlers中,而过期的放在了_expiredHandlers,并在合适的时候释放销毁。注意,过期不意味着要立即销毁,只是不再重用,即不再分配给新的HttpClient实例了。
那为什么不让创建好的HttpMessageHandler一直重用,干嘛要销毁呢?它的原理与各种池(如数据库连接池、线程池)类似,就是为了保证套接字连接在空闲的时候能够被及时关闭,而不是长时间保持打开的状态,白白占用资源。
总结
现在,我们已经对HttpClient和IHttpClientFactory有了一个清晰的认识,我们简单总结一下:
HttpClient是当前.NET版本中发送Http请求的首选HttpClient提供了很多异步Rest方法,非常适合当下的异步编程模型HttpClient旨在实例化一次,并在应用程序的整个生命周期内重复使用。- 直接创建
HttpClient实例,很容易被错误使用,建议通过IHttpClientFactory来创建 HttpClient是对HttpMessageHandler的包装,默认使用HttpMessageHandler的子类HttpClientHandler,而HttpClientHandler也只是对SocketsHttpHandler的简单包装(不讨论WASM)- 通过
IHttpClientFactory,我们可以方便地创建命名客户端、类型化客户端等 IHttpClientFactory通过创建多个HttpClient实例,但多个实例重用同一个HttpMessageHandler来优化HttpClient的创建
理解ASP.NET Core - 发送Http请求(HttpClient)的更多相关文章
- .Net Core 发送https请求/.net core 调用数字证书 使用X509Certificate2
.Net Core 发送https请求 .net core 调用数字证书 使用X509Certificate2 .NET下面的 .netfromwork使用和asp.net core下使用方式不一样 ...
- 理解ASP.NET Core - [02] Middleware
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 中间件 先借用微软官方文档的一张图: 可以看到,中间件实际上是一种配置在HTTP请求管道中,用 ...
- 理解ASP.NET Core - 错误处理(Handle Errors)
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或[点击此处查看全文目录](https://www.cnblogs.com/xiaoxiaotank/p/151852 ...
- 理解ASP.NET Core - 基于Cookie的身份认证(Authentication)
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 概述 通常,身份认证(Authentication)和授权(Authorization)都会放 ...
- 理解ASP.NET Core - 基于JwtBearer的身份认证(Authentication)
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 在开始之前,如果你还不了解基于Cookie的身份认证,那么建议你先阅读<基于Cookie ...
- 理解 ASP.NET Core: 处理管道
理解 ASP.NET Core 处理管道 在 ASP.NET Core 的管道处理部分,实现思想已经不是传统的面向对象模式,而是切换到了函数式编程模式.这导致代码的逻辑大大简化,但是,对于熟悉面向对象 ...
- 理解ASP.NET Core - [01] Startup
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 准备工作:一份ASP.NET Core Web API应用程序 当我们来到一个陌生的环境,第一 ...
- 理解ASP.NET Core - [03] Dependency Injection
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 依赖注入 什么是依赖注入 简单说,就是将对象的创建和销毁工作交给DI容器来进行,调用方只需要接 ...
- 理解ASP.NET Core - [04] Host
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 本文会涉及部分 Host 相关的源码,并会附上 github 源码地址,不过为了降低篇幅,我会 ...
随机推荐
- redis 使用详解
前戏: 又到了最喜欢的前戏部分,这个前戏可能有点长: Nosql和sql的区别 存储结构与mysql这一种关系型数据库完全不同,nosql存储的是KV形式 应用场景不同,sql支持关系复杂的数据查询, ...
- 学习MFS(四)
一.搭建Master Server 1.安装相关编译器.工具包 [root@master ~]# yum -y install gcc gcc-c++ zlib-devel 2.创建进程用户 [roo ...
- Leetcode1——两数之和 详细解析
Leetcode1--两数之和 题目分类:数组.哈希表的应用 1. 题目描述 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数 ...
- Numpy使用Matplotlib实现可视化绘图
Numpy使用Matplotlib实现可视化绘图 可以直接将Numpy的数组传给Matplotlib实现可视化绘图: 曲线图 饼图 柱状图 直方图 1. 绘制正弦曲线 2. 绘制饼图 3. 柱状图 4 ...
- MCU选型
含义: MCU(Micro Controller Unit)中文名称为微控制单元,又称单片微型计算机(Single Chip Microcomputer),是指随着大规模集成电路的出现及其发展,将计算 ...
- CSS详细解读定位
一 前言 CSS定位是CSS布局只能够重要的一环.本篇文章带你解读定位属性,可以让你更加深入的理解定位带来的一些特性,熟练使用CSS布局. 二 正文 1.文档流布局的概念 将窗体自上而下分成一行行, ...
- css布局中左侧固定右侧自适应
float 单一层浮动法左侧固定成100px; 则核心代码 左侧:width:100px;float:left; 右侧 width:auto;margin-left:100px;绝大浏览器是没有任何问 ...
- js 简易模块加载器 示例分析
前端模块化 关注前端技术发展的各位亲们,肯定对模块化开发这个名词不陌生.随着前端工程越来越复杂,代码越来越多,模块化成了必不可免的趋势. 各种标准 由于javascript本身并没有制定相关标准(当然 ...
- 解决HDFS无法启动namenode,报错Premature EOF from inputStream;Failed to load FSImage file, see error(s) above for more info
一.情况描述 启动hadoop后发现无法打开hdfs web界面,50070打不开,于是jps发现少了一个namenode: 查看日志信息,发现如下报错: 2022-01-03 23:54:10,99 ...
- JavaScript 中 append()、prepend()、after()、before() 的区别
内容 append().prepend().after().before() 的区别 jQuery 操作 DOM 之添加节点 方法名 作用 $(selector).append() 指定元素内部添加, ...