CodeSnippet.Cn
代码片段
Csharp
架构设计
.NetCore
西班牙语
kubernetes
MySql
Redis
Algorithm
Ubuntu
Linux
Other
.NetMvc
VisualStudio
Git
pm
Python
WPF
java
Plug-In
分布式
CSS
微服务架构
JavaScript
DataStructure
Shared
理解ASP.NET Core - 发送Http请求(HttpClient)
0
.NetCore
架构设计
小笨蛋
发布于:2022年08月02日
更新于:2022年08月29日
209
#custom-toc-container
### 前言 在.NET中,我们有很多发送Http请求的手段,如 `HttpWebRequest`、`WebClient` 以及 `HttpClient`。 在进入正文之前,先简单了解一下前2个: **HttpWebRequest** ```csharp namespace System.Net { public class HttpWebRequest : WebRequest, ISerializable { } } ``` ------------ `HttpWebRequest`位于System.Net命名空间下,继承自抽象类`WebRequest`,是.NET中最早、最原始地用于操作Http请求的类。相对来说,该类提供的方法更接近于底层,所以它的使用较为繁琐,对于开发者的水平要求是比较高的。 **WebClient** ```csharp namespace System.Net { public class WebClient : Component { } } ``` 同样的,`WebClient`也位于System.Net命名空间下,它主要是对`WebRequest`进行了一层封装,简化了常用任务场景的使用,如文件上传、文件下载、数据上传、数据下载等,并提供了一系列事件。 不过,虽然`HttpWebRequest`和`WebClient`仍然可用,**但官方建议,若没有特殊要求,不要使用他俩,而应该使用HttpClient**。那`HttpClient`是什么呢? ### HttpClient ```csharp 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异常。 我们一起来看一个错误的示例: ```csharp public class ValuesController : ControllerBase { [HttpGet("WrongUsage")] public async Task
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),你会发现程序虽然已经退出了,但是连接并没有像我们所预期的那样立即关闭: ```csharp > 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 ``` 下面是一个较为合理的示例: ```csharp public class ValuesController : ControllerBase { private static readonly HttpClient _httpClient; static ValuesController() { // 复用同一个实例 _httpClient = new HttpClient(); } } ``` 可以看出,`HttpClient`很容易被错误使用,并且,即使是上面的正确示例,仍然有很多待优化的地方。因此,为了解决这个问题,`IHttpClientFactory`诞生了。 ### IHttpClientFactory 看名字就知道了,`IHttpClientFactory`可以帮我们创建所需要的`HttpClient`实例,我们无须关心实例的创建过程。与`HttpClient`一样,位于System.Net.Http命名空间下。 下面先了解一下它的一些用法。 #### 基础用法 首先,注册HttpClientFactory相关的服务 ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddHttpClient(); ``` 然后,构造函数注入`IHttpClientFactory`,通过CreateClient()创建Client实例。 ```csharp public class ValuesController : ControllerBase { private readonly IHttpClientFactory _httpClientFactory; public ValuesController(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } [HttpGet] public async Task
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}"; } } ``` 输出: ```csharp { "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的客户端: ```csharp // 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
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}"; } ``` 输出: ```csharp { "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`接口的调用: ```csharp public class JsonPlaceholderClient { private readonly HttpClient _httpClient; // 直接注入 HttpClient public JsonPlaceholderClient(HttpClient httpClient) { _httpClient = httpClient; } public async Task
GetPost(int id) => await _httpClient.GetFromJsonAsync
($"/posts/{id}"); } ``` 为了让DI容器知道要将哪个`HttpClient`实例注入到JsonPlaceholderClient的构造函数,我们需要配置一下服务: ```csharp builder.Services.AddHttpClient
(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`,使用起来就好像在调用本地服务似的: ```csharp public class ValuesController : ControllerBase { private readonly JsonPlaceholderClient _jsonPlaceholderClient; public ValuesController(JsonPlaceholderClient jsonPlaceholderClient) { _jsonPlaceholderClient = jsonPlaceholderClient; } [HttpGet("typed")] public async Task
GetTyped() { var post = await _jsonPlaceholderClient.GetPost(1); return post; } } ``` #### 借助第三方库生成的客户端 一般来说,类型化的客户端已经大大简化了我们使用`HttpClient`的步骤和难度,不过,我们还可以借助第三方库再次简化我们的代码:我们只需要定义要调用的服务接口,第三方库会生成代理类。 常用的第三方库有以下两个: - [Refit](https://github.com/reactiveui/refit "Refit") - [WebApiClientCore](https://github.com/dotnetcore/WebApiClient "WebApiClientCore") 这两个第三方库的使用方式非常类似,由于我比较熟悉`WebApiClientCore`,所以后面的示例均使用它进行演示。 首先,安装Nuget包: `Install-Package WebApiClientCore` 接着,创建一个接口`IJsonPlaceholderApi`: ```csharp [Header("User-Agent", "HttpClientFactory-Sample-Api")] [Header("Custom-Header", "Custom-Value")] public interface IJsonPlaceholderApi { [HttpGet("/posts/{id}")] Task
GetPost(int id); } ``` 怎么样,看起来是不是很像在写Web Api? 对了,别忘了进行服务注册: ```csharp builder.Services.AddHttpApi
( o => { o.HttpHost = new Uri("https://jsonplaceholder.typicode.com/"); o.UseDefaultUserAgent = false; }); ``` 最后,我们就可以更方便地用它了: ```csharp public class ValuesController : ControllerBase { private readonly IJsonPlaceholderApi _jsonPlaceholderApi; public ValuesController(IJsonPlaceholderApi jsonPlaceholderApi) { _jsonPlaceholderApi = jsonPlaceholderApi; } [HttpGet("api")] public async Task
GetApi() { var post = await _jsonPlaceholderApi.GetPost(1); return post; } } ``` ### HttpClient设计原理 上面我们提到过:`HttpClient`旨在实例化一次,并在应用程序的整个生命周期内重复使用。如果每次请求都实例化一个`HttpClient`,由于`Dispose`并不会立即释放套接字,那么当短时间内有大量请求时,服务器的套接字数就会被耗尽,从而引发`SocketException`异常。 为了能够真正理解这句话,我们一起看一下`HttpClient`的是如何发送请求并处理响应结果的。 下面,我们先看下`HttpClient`的基本结构: > 按照惯例,为了方便理解,后续列出的源码中我已经删除了一些不是那么重要的代码。 ```csharp 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
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
SendAsync(HttpRequestMessage request) => SendAsync(request, DefaultCompletionOption, CancellationToken.None); public override Task
SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => SendAsync(request, DefaultCompletionOption, cancellationToken); public Task
SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption) => SendAsync(request, completionOption, CancellationToken.None); public Task
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: ```csharp 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
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
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平台上行为一致 ```csharp [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
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
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设计方式 我们先从服务注册看起: ```csharp public static class HttpClientFactoryServiceCollectionExtensions { public static IServiceCollection AddHttpClient(this IServiceCollection services) { services.AddLogging(); services.AddOptions(); // 核心服务 services.TryAddTransient
(); services.TryAddSingleton
(); services.TryAddSingleton
(serviceProvider => serviceProvider.GetRequiredService
()); services.TryAddSingleton
(serviceProvider => serviceProvider.GetRequiredService
()); // 类型化客户端服务 services.TryAdd(ServiceDescriptor.Transient(typeof(ITypedHttpClientFactory<>), typeof(DefaultTypedHttpClientFactory<>))); services.TryAdd(ServiceDescriptor.Singleton(typeof(DefaultTypedHttpClientFactory<>.Cache), typeof(DefaultTypedHttpClientFactory<>.Cache))); services.TryAddEnumerable(ServiceDescriptor.Singleton
()); services.TryAddSingleton(new HttpClientMappingRegistry()); // 默认注册一个名字为空字符串的 HttpClient 实例 services.TryAddTransient(s => s.GetRequiredService
().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
(validateSingleType: true); return builder; } } ``` 很显然,HttpMessageHandlerBuilder的作用就是创建HttpMessageHandler实例,默认实现为DefaultHttpMessageHandlerBuilder IHttpMessageHandlerBuilderFilter会在DefaultHttpClientFactory中用到,它可以在HttpMessageHandlerBuilder.Build调用之前对HttpMessageHandlerBuilder进行一些初始化操作。 `IHttpClientFactory`接口的默认实现是DefaultHttpClientFactory: ```csharp internal class DefaultHttpClientFactory : IHttpClientFactory, IHttpMessageHandlerFactory { private readonly IServiceProvider _services; private readonly Func
> _entryFactory; // 有效的Handler对象池,使用Lazy来保证每个命名客户端具有唯一的 HttpMessageHandler 实例 internal readonly ConcurrentDictionary
> _activeHandlers; // 过期的Handler集合 internal readonly ConcurrentQueue
_expiredHandlers; public DefaultHttpClientFactory( IServiceProvider services, IServiceScopeFactory scopeFactory, ILoggerFactory loggerFactory, IOptionsMonitor
optionsMonitor, IEnumerable
filters) { _services = services; _activeHandlers = new ConcurrentDictionary
>(StringComparer.Ordinal); _entryFactory = (name) => { return new Lazy
(() => { 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
(); 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处理之前和之后加一些自己的特定逻辑。 ```csharp 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
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`的创建
这里⇓感觉得写点什么,要不显得有点空,但还没想好写什么...
返回顶部
About
京ICP备13038605号
© 代码片段 2024