https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0#consumption-patterns
在 ASP.NET Core 中使用 IHttpClientFactory 发出 HTTP 请求作者:Kirk Larkin、Steve Gordon、Glenn Condron和Ryan Nowak。
可以注册IHttpClientFactory并将其用于配置和创建应用中的HttpClient实例。IHttpClientFactory
的优势如下:
- 提供一个中心位置,用于命名和配置逻辑
HttpClient
实例。 例如,可注册和配置名为 github 的客户端,使其访问GitHub。 可以注册一个默认客户端用于一般性访问。 - 通过
HttpClient
中的委托处理程序来编码出站中间件的概念。 提供基于 Polly 的中间件的扩展,以利用HttpClient
中的委托处理程序。 - 管理基础
HttpClientMessageHandler
实例的池和生存期。 自动管理可避免手动管理HttpClient
生存期时出现的常见 DNS(域名系统)问题。 - (通过
ILogger
)添加可配置的记录体验,以处理工厂创建的客户端发送的所有请求。
此主题版本中的示例代码使用System.Text.Json来对 HTTP 响应中返回的 JSON 内容进行反序列化。 对于使用Json.NET
和ReadAsAsync<T>
的示例,请使用版本选择器选择此主题的 2.x 版本。
消耗模式
在应用中可以通过以下多种方式使用IHttpClientFactory
:
最佳方法取决于应用要求。
基本用法
可以通过调用AddHttpClient
来注册IHttpClientFactory
:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
// Remaining code deleted for brevity.
可以使用依赖项注入 (DI)来请求IHttpClientFactory
。 以下代码使用IHttpClientFactory
来创建HttpClient
实例:
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
Branches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(responseStream);
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
像前面的示例一样,使用IHttpClientFactory
是重构现有应用的好方法。 这不会影响HttpClient
的使用方式。 在现有应用中创建HttpClient
实例的位置,使用对CreateClient的调用替换这些匹配项。
命名客户端
在以下情况下,命名客户端是一个不错的选择:
- 应用需要
HttpClient
的许多不同用法。 - 许多
HttpClient
具有不同的配置。
可以在Startup.ConfigureServices
中注册时指定命名HttpClient
的配置:
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
在上述代码中,客户端配置如下:
- 基址为
https://api.github.com/
。 - 使用 GitHub API 需要的两个标头。
CreateClient
每次调用CreateClient时:
- 创建
HttpClient
的新实例。 - 调用配置操作。
要创建命名客户端,请将其名称传递到CreateClient
中:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
PullRequests = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubPullRequest>>(responseStream);
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
在上述代码中,请求不需要指定主机名。 代码可以仅传递路径,因为采用了为客户端配置的基址。
类型化客户端
类型化客户端:
- 提供与命名客户端一样的功能,不需要将字符串用作密钥。
- 在使用客户端时提供 IntelliSense 和编译器帮助。
- 提供单个位置来配置特定
HttpClient
并与其进行交互。 例如,可以使用单个类型化客户端:- 对于单个后端终结点。
- 封装处理终结点的所有逻辑。
- 使用 DI 且可以被注入到应用中需要的位置。
类型化客户端在构造函数中接受HttpClient
参数:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
return await Client.GetFromJsonAsync<IEnumerable<GitHubIssue>>(
"/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
}
}
在上述代码中:
- 配置转移到了类型化客户端中。
HttpClient
对象公开为公共属性。
可以创建特定于 API 的方法来公开HttpClient
功能。 例如,创建GetAspNetDocsIssues
方法来封装代码以检索未解决的问题。
以下代码调用Startup.ConfigureServices
中的AddHttpClient来注册类型化客户端类:
services.AddHttpClient<GitHubService>();
使用 DI 将类型客户端注册为暂时客户端。 在上述代码中,AddHttpClient
将GitHubService
注册为暂时性服务。 此注册使用工厂方法执行以下操作:
- 创建
HttpClient
的实例。 - 创建
GitHubService
的实例,将HttpClient
的实例传入其构造函数。
可以直接插入或使用类型化客户端:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
public bool HasIssue => LatestIssues.Any();
public bool GetIssuesError { get; private set; }
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
catch(HttpRequestException)
{
GetIssuesError = true;
LatestIssues = Array.Empty<GitHubIssue>();
}
}
}
可以在Startup.ConfigureServices
中注册时指定类型化客户端的配置,而不是在类型化客户端的构造函数中指定:
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
可以将HttpClient
封装在类型化客户端中, 定义一个在内部调用HttpClient
实例的方法,而不是将其公开为属性:
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<string>>(responseStream);
}
}
在上述代码中,HttpClient
存储在私有字段中。 通过公共GetRepos
方法访问HttpClient
。
生成的客户端
IHttpClientFactory
可结合第三方库(例如Refit)使用。 Refit 是.NET 的 REST 库。 它将 REST API 转换为实时接口。RestService
动态生成该接口的实现,使用HttpClient
进行外部 HTTP 调用。
定义了接口和答复来代表外部 API 及其响应:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
可以添加类型化客户端,使用 Refit 生成实现:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
services.AddControllers();
}
可以在必要时使用定义的接口,以及由 DI 和 Refit 提供的实现:
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
public ValuesController(IHelloClient client)
{
_client = client;
}
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
发出 POST、PUT 和 DELETE 请求
在前面的示例中,所有 HTTP 请求均使用 GET HTTP 谓词。HttpClient
还支持其他 HTTP 谓词,其中包括:
- POST
- PUT
- 删除
- PATCH
有关受支持的 HTTP 谓词的完整列表,请参阅HttpMethod。
下面的示例演示如何发出 HTTP POST 请求:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
在前面的代码中,CreateItemAsync
方法:
- 使用
System.Text.Json
将TodoItem
参数序列化为 JSON。 这将使用JsonSerializerOptions的实例来配置序列化过程。 - 创建StringContent的实例,以打包序列化的 JSON 以便在 HTTP 请求的正文中发送。
- 调用PostAsync将 JSON 内容发送到指定的 URL。 这是添加到HttpClient.BaseAddress的相对 URL。
- 如果响应状态代码不指示成功,则调用EnsureSuccessStatusCode引发异常。
HttpClient
还支持其他类型的内容。 例如,MultipartContent和StreamContent。 有关受支持的内容的完整列表,请参阅HttpContent。
下面的示例演示了一个 HTTP PUT 请求:
public async Task SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
前面的代码与 POST 示例非常相似。SaveItemAsync
方法调用PutAsync而不是PostAsync
。
下面的示例演示了一个 HTTP DELETE 请求:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponse.EnsureSuccessStatusCode();
}
在前面的代码中,DeleteItemAsync
方法调用DeleteAsync。 由于 HTTP DELETE 请求通常不包含正文,因此DeleteAsync
方法不提供接受HttpContent
实例的重载。
要详细了解如何将不同的 HTTP 谓词用于HttpClient
,请参阅HttpClient。
出站请求中间件
HttpClient
具有委托处理程序的概念,这些委托处理程序可以链接在一起,处理出站 HTTP 请求。IHttpClientFactory
:
简化定义应用于各命名客户端的处理程序。
支持注册和链接多个处理程序,以生成出站请求中间件管道。 每个处理程序都可以在出站请求前后执行工作。 此模式:
- 类似于 ASP.NET Core 中的入站中间件管道。
- 提供一种机制来管理有关 HTTP 请求的横切关注点,例如:
- 缓存
- 错误处理
- 序列化
- 日志记录
创建委托处理程序:
- 派生自DelegatingHandler。
- 重写SendAsync。 在将请求传递至管道中的下一个处理程序之前执行代码:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
上述代码检查请求中是否存在X-API-KEY
标头。 如果缺失X-API-KEY
,则返回BadRequest。
可使用Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler将多个处理程序添加到HttpClient
的配置中:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5001/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
// Remaining code deleted for brevity.
在上述代码中通过 DI 注册了ValidateHeaderHandler
。 注册后可以调用AddHttpMessageHandler,传入标头的类型。
可以按处理程序应该执行的顺序注册多个处理程序。 每个处理程序都会覆盖下一个处理程序,直到最终HttpClientHandler
执行请求:
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
在出站请求中间件中使用 DI
当IHttpClientFactory
创建新的委托处理程序时,它使用 DI 来完成处理程序的构造函数参数。IHttpClientFactory
为每个处理程序创建单独的 DI 范围,当处理程序使用限定范围的服务时,这可能导致意外的行为。
例如,请考虑下面的接口及其实现,它将任务表示为带有标识符OperationId
的操作:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
顾名思义,使用限定范围的生存期向 DI 注册IOperationScoped
:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(options =>
options.UseInMemoryDatabase("TodoItems"));
services.AddHttpContextAccessor();
services.AddHttpClient<TodoClient>((sp, httpClient) =>
{
var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;
// For sample purposes, assume TodoClient is used in the context of an incoming request.
httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
httpRequest.Host, httpRequest.PathBase));
httpClient.Timeout = TimeSpan.FromSeconds(5);
});
services.AddScoped<IOperationScoped, OperationScoped>();
services.AddTransient<OperationHandler>();
services.AddTransient<OperationResponseHandler>();
services.AddHttpClient("Operation")
.AddHttpMessageHandler<OperationHandler>()
.AddHttpMessageHandler<OperationResponseHandler>()
.SetHandlerLifetime(TimeSpan.FromSeconds(5));
services.AddControllers();
services.AddRazorPages();
}
以下委托处理程序消耗并使用IOperationScoped
来设置传出请求的X-OPERATION-ID
标头:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationService;
public OperationHandler(IOperationScoped operationScoped)
{
_operationService = operationScoped;
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
在HttpRequestsSample
下载] 中,导航到/Operation
并刷新页面。 每个请求的请求范围值发生更改,但处理程序范围值仅每 5 秒钟更改一次。
处理程序可依赖于任何作用域的服务。 处理程序依赖的服务会在处置处理程序时得到处置。
使用以下方法之一将每个请求状态与消息处理程序共享:
- 使用HttpRequestMessage.Properties将数据传递到处理程序中。
- 使用IHttpContextAccessor访问当前请求。
- 创建自定义AsyncLocal<T>存储对象以传递数据。
使用基于 Polly 的处理程序
IHttpClientFactory
与第三方库Polly集成。 Polly 是适用于 .NET 的全面恢复和临时故障处理库。 开发人员通过它可以表达策略,例如以流畅且线程安全的方式处理重试、断路器、超时、Bulkhead 隔离和回退。
提供了扩展方法,以实现将 Polly 策略用于配置的HttpClient
实例。 Polly 扩展支持将基于 Polly 的处理程序添加到客户端。 Polly 需要Microsoft.Extensions.Http.PollyNuGet 包。
处理临时故障
错误通常在暂时执行外部 HTTP 调用时发生。AddTransientHttpErrorPolicy允许定义一个策略来处理暂时性错误。 使用AddTransientHttpErrorPolicy
配置的策略处理以下响应:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
提供对PolicyBuilder
对象的访问权限,该对象配置为处理表示可能的临时故障的错误:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
// Remaining code deleted for brevity.
上述代码中定义了WaitAndRetryAsync
策略。 请求失败后最多可以重试三次,每次尝试间隔 600 ms。
动态选择策略
提供了扩展方法来添加基于 Polly 的处理程序,例如AddPolicyHandler。 以下AddPolicyHandler
重载检查请求以确定要应用的策略:
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
在上述代码中,如果出站请求为 HTTP GET,则应用 10 秒超时。 其他所有 HTTP 方法应用 30 秒超时。
添加多个 Polly 处理程序
这对嵌套 Polly 策略很常见:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
在上面的示例中:
- 添加了两个处理程序。
- 第一个处理程序使用AddTransientHttpErrorPolicy添加重试策略。 若请求失败,最多可重试三次。
- 第二个
AddTransientHttpErrorPolicy
调用添加断路器策略。 如果尝试连续失败了 5 次,则会阻止后续外部请求 30 秒。 断路器策略处于监控状态。 通过此客户端进行的所有调用都共享同样的线路状态。
从 Polly 注册表添加策略
管理常用策略的一种方法是一次性定义它们并使用PolicyRegistry
注册它们。
在以下代码中:
- 添加了“常规”和“长”策略。
- AddPolicyHandlerFromRegistry从注册表中添加“常规”和“长”策略。
public void ConfigureServices(IServiceCollection services)
{
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regularTimeoutHandler")
.AddPolicyHandlerFromRegistry("regular");
services.AddHttpClient("longTimeoutHandler")
.AddPolicyHandlerFromRegistry("long");
// Remaining code deleted for brevity.
有关IHttpClientFactory
和 Polly 集成的详细信息,请参阅Polly Wiki。
HttpClient 和生存期管理
每次对IHttpClientFactory
调用CreateClient
都会返回一个新HttpClient
实例。 每个命名客户端都创建一个HttpMessageHandler。 工厂管理HttpMessageHandler
实例的生存期。
IHttpClientFactory
将工厂创建的HttpMessageHandler
实例汇集到池中,以减少资源消耗。 新建HttpClient
实例时,可能会重用池中的HttpMessageHandler
实例(如果生存期尚未到期的话)。
由于每个处理程序通常管理自己的基础 HTTP 连接,因此需要池化处理程序。 创建超出必要数量的处理程序可能会导致连接延迟。 部分处理程序还保持连接无期限地打开,这样可以防止处理程序对 DNS(域名系统)更改作出反应。
处理程序的默认生存期为两分钟。 可在每个命名客户端上重写默认值:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
// Remaining code deleted for brevity.
HttpClient
实例通常可视为无需处置的 .NET 对象。 处置既取消传出请求,又保证在调用Dispose后无法使用给定的HttpClient
实例。IHttpClientFactory
跟踪和处置HttpClient
实例使用的资源。
保持各个HttpClient
实例长时间处于活动状态是在IHttpClientFactory
推出前使用的常见模式。 迁移到IHttpClientFactory
后,就无需再使用此模式。
IHttpClientFactory 的替代项
通过在启用了 DI 的应用中使用IHttpClientFactory
,可避免:
- 通过共用
HttpMessageHandler
实例,解决资源耗尽问题。 - 通过定期循环
HttpMessageHandler
实例,解决 DNS 过时问题。
此外,还有其他方法使用生命周期长的SocketsHttpHandler实例来解决上述问题。
- 在应用启动时创建
SocketsHttpHandler
的实例,并在应用的整个生命周期中使用它。 - 根据 DNS 刷新时间,将PooledConnectionLifetime配置为适当的值。
- 根据需要,使用
new HttpClient(handler, disposeHandler: false)
创建HttpClient
实例。
上述方法使用IHttpClientFactory
解决问题的类似方式解决资源管理问题。
SocketsHttpHandler
在HttpClient
实例之间共享连接。 此共享可防止套接字耗尽。SocketsHttpHandler
会根据PooledConnectionLifetime
循环连接,避免出现 DNS 过时问题。
Cookies
共用HttpMessageHandler
实例将导致共享CookieContainer
对象。 意外的CookieContainer
对象共享通常会导致错误的代码。 对于需要 cookie 的应用,请考虑执行以下任一操作:
- 禁用自动 cookie 处理
- 避免
IHttpClientFactory
调用ConfigurePrimaryHttpMessageHandler以禁用自动 cookie 处理:
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Logging
通过IHttpClientFactory
创建的客户端记录所有请求的日志消息。 在日志记录配置中启用合适的信息级别可以查看默认日志消息。 仅在跟踪级别包含附加日志记录(例如请求标头的日志记录)。
用于每个客户端的日志类别包含客户端名称。 例如,名为 MyNamedClient 的客户端记录类别为“System.Net.Http.HttpClient.MyNamedClient.LogicalHandler”的消息。 后缀为 LogicalHandler 的消息在请求处理程序管道外部发生。 在请求时,在管道中的任何其他处理程序处理请求之前记录消息。 在响应时,在任何其他管道处理程序接收响应之后记录消息。
日志记录还在请求处理程序管道内部发生。 在 MyNamedClient 示例中,这些消息的日志类别为“System.Net.Http.HttpClient.MyNamedClient.ClientHandler”。 在请求时,在所有其他处理程序运行后,以及刚好要发出请求之前记录消息。 在响应时,此日志记录包含响应在通过处理程序管道被传递回去之前的状态。
在管道内外启用日志记录,可以检查其他管道处理程序做出的更改。 这可能包含对请求标头的更改,或者对响应状态代码的更改。
通过在日志类别中包含客户端名称,可以对特定的命名客户端筛选日志。
配置 HttpMessageHandler
控制客户端使用的内部HttpMessageHandler
的配置是有必要的。
在添加命名客户端或类型化客户端时,会返回IHttpClientBuilder
。ConfigurePrimaryHttpMessageHandler扩展方法可以用于定义委托。 委托用于创建和配置客户端使用的主要HttpMessageHandler
:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
// Remaining code deleted for brevity.
在控制台应用中使用 IHttpClientFactory
在控制台中,将以下包引用添加到项目中:
如下示例中:
- IHttpClientFactory已在泛型主机的服务容器中注册。
MyService
从服务创建客户端工厂实例,用于创建HttpClient
。HttpClient
用于检索网页。Main
可创建作用域来执行服务的GetPage
方法,并将网页内容的前 500 个字符写入控制台。
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
标头传播中间件
标头传播是一个 ASP.NET Core 中间件,可将 HTTP 标头从传入请求传播到传出 HTTP 客户端请求。 使用标头传播:
在
Startup
中配置中间件和HttpClient
:C#public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
客户端在出站请求中包含配置的标头:
C#var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);