翻译 - ASP.NET Core 基本知识 - 创建 HTTP 请求(Make HTTP Requests)

翻译自 https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0

可以在应用程序中注册一个 IHttpClientFactory 来配置和创建 HttpClient 实例。 IHttpClientFactory 提供了以下的便利:

  • 提供一个集中命名和配置逻辑上的 HttpClient 实例。例如,一个被命名为 github 的客户端可以被注册和配置用来访问 GitHub。一个默认的客户端可以被注册来处理一般的访问
  • 通过 HttpClient 中的代理处理器来编纂外出中间件的概念。为 Polly-based 的中间件提供扩展可以利用 HttpClient 中的代理处理的好处
  • 管理 HttpClientMessageHandler 之下的池和生命周期。自动管理可以避免通常的 DNS (Domain Name System)问题,这些问题会在手动管理 HttpClient 生命周期时出现
  • 为所有发送到由工厂创建的客户端的请求添加一个可配置的日志体验

本话题中的示例代码使用 System.Text.Json 解析 JSON 内容返回到 HTTP 相应中。对于使用 Json.NET 和 ReadAsAsyn 的示例,选择该话题的 2.x 版本。

 消费模式

有以下集中方式在应用程序中使用 IHttpClientFactory:

最佳的方法依赖于应用程序的要求。

Basic usage

IHttpClientFactory 可以通过 AddHttpClient 注册:

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.

可以使用 dependency injection (DI) 来请求一个 IHttoClientFactory 实例。下面的代码使用 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 替换掉即可。

Named clients

当以下情况时,Named clients 是一个不错的选择:

  • 应用程序需要很多不同的 HttpClient 的使用
  • 许多 HttpClients 使用不同的配置

可以在 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");
});

在上面的代码中,client 配置为:

  • 基本的地址是 https://api.github.com/
  • GitHub API 要求两个头部信息

CreateClient

CreateClient 每次被调用时:

  • 一个新的 HttpClient 实例会被创建
  • 配置方法被调用

要创建一个命名的 client,需要传递一个名称给 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>();
        }
    }
}

在上面的代码中,请求不必指定一个主机名称。代码可以仅仅传递路径,因为基本的地址已经为客户端配置使用了。

Typed clients

Typed clients:

  • 提供和命名客户端同样的功能,而不需要使用字符串作为键
  • 在使用客户端时,提供智能感知和编译帮助
  • 提供一个单一的位置配置和使用一个特殊的 HttpClient 交互
    例如,一个单一的类型客户端可能被使用到:
    - 对于一个单一的后端 endpoint
    - 封装处理 endpoint 的所有逻辑
  • 使用 DI 工作,在应用程序中需要时可以被注入

一个 typed client 的构造方法接收一个 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()
    {
        var response = await Client.GetAsync(
            "/repos/dotnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");

        response.EnsureSuccessStatusCode();

        using var responseStream = await response.Content.ReadAsStreamAsync();
        return await JsonSerializer.DeserializeAsync
            <IEnumerable<GitHubIssue>>(responseStream);
    }
}

在上面的代码中:

  • 配置被移动到 typed client
  • HttpClient 对象暴露为一个公共的属性

API-special 方法可以被创建,用来暴露 HttpClient 的功能。例如,GetAspNetDocsIssues 方法分装了获取开放 issues 的代码。

下面的代码在 Startup.ConfigureServies 调用 AddHttpClient 注册了一个 typed client 类:

services.AddHttpClient<GitHubService>();

typed client 使用 DI 被注册为一个短暂的服务。在上面的代码中,AddHttpClient 注册 GitHubSerbice 作为一个短暂的服务。注册使用了一个工厂方法:

  1. 创建 HttpClient 的实例
  2. 创建一个 GitHubService 的实例,传递 HttpClient 实例到它的构造方法中

typed client 可以被注入和直接使用:

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>();
        }
    }
}

一个 typed client 的配置可以在 Startup.ConfigureServies 注册的过程中指定,而不是在 typed client 的构造方法中:

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 可以封装在一个 typed client 中。而不是作为一个属性暴露,在实例内部定义一个调用 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。

 Generated clients

IHttpClientFactory 可以结合第三方库使用,例如 Refit。Refit 是 .NET 的一个 REST 库。它转换 REST APIs 到实时的接口中。接口的实现是由 RestService 动态实现的,使用 HttpClient 创建外部 HTTP 调用。

定义一个接口和 reply 类来表示外部 API 和 它的相应:

public interface IHelloClient
{
    [Get("/helloworld")]
    Task<Reply> GetMessageAsync();
}

public class Reply
{
    public string Message { get; set; }
}

可以添加一个 typed client,使用 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
  • DELETE
  • 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 方法:

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 实例的重载方法。

了解更多关于 HttpClient 使用不同 HTTP 动词,查看 HttpClient

Outgoing 请求中间件

HttpClient 中有代理处理程序的概念,这对于 outgoing HTTP 请求可以链接在一起。IHttpClientFactory:

  • 简化应用到每一个命名 client 的处理程序的定义
  • 支持注册和链式多个处理程序建立一个 outgoing 请求中间件管道。每一个处理程序都能够在 outgoing 请求之前和之后工作。这种模式:
    - 和 ASP.NET Core 中的入站中间件管道相似
    - 提供了一种管理 HTTP 请求周围 cross-cutting 的考虑的机制,例如
      - 缓存
          - 错误处理
          - 序列化
          - 日志

     

创建一个代理处理程序:

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>();

在 outgoing 请求中间件中使用 DI

当 IHttpClientFactory 创建一个新的代理处理程序,它使用 DI 填充处理程序的构造方法的参数。 IHttpClientFactory 创建一个隔离的 DI 为每一个处理程序,这在当一个处理程序作为一个 scoped 服务时,这可能导致意外的行为。

例如,考虑下面的接口和它的实现,使用标识符把一个任务作为一个操作,OperationId:

public interface IOperationScoped 
{
    string OperationId { get; }
}

public class OperationScoped : IOperationScoped
{
    public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}

顾名思义,IOperationScoped 使用 DI 注册为一个 scoped 生命周期的服务:

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 为 outgoing 请求设置了 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 download 中,导航到 /Operation,刷新页面。请求审视了每一个请求的值得变化,但是处理程序仅仅每 5 秒钟审视一次值得变化。

处理程序审视时可以依赖服务。处理程序依赖的服务在处理程序释放时也会随之释放掉。

使用下面的一种方法使用消息处理程序分享每一次请求的状态:

使用 Polly-based 处理程序

IHttpClientFactory 集成了第三方库 Polly。Polly 是 .NET 的一个综合复原和短暂错误处理的库。它允许开发者使用像 Retry,Cricuit,Breaker,timeout,Bulkhead Isolation 和 Fallback 流畅的和线程安全管理的策略。

提供的扩展方法保证了可以使用配置好的 HttpClient 实例来使用 Polly 策略。Polly 扩展支持添加 Polly-based 处理程序到 clients。Polly 要求安装 Microsoft.Extensions.Http.Polly NuGet 包。

处理短暂错误

当外部 HTTP 调用出现的错误一般都是短暂的。AddTransientHttpErrorPolicy 允许定义一个策略去处理短暂错误。使用 AddTransientHttpErrorPolicy 配置的策略处理以下响应:

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-based 处理程序,例如, 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);

在上面的代码中,如果 outgoing 请求是一个 HTTP GET 请求,10s 的超时会被应用。对于任何的其它的 HTTP 方法,使用 30s 的超时。

添加多个 Polly 处理程序

 通常会嵌套 Polly 策略:

services.AddHttpClient("multiplepolicies")
    .AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
    .AddTransientHttpErrorPolicy(
        p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

在上面的例子中:

  • 添加了两个处理程序
  • 第一个处理程序使用 AddTransientHttpErrorPolicy 添加了一个重试策略。失败的请求将会重试三次
  • 第二个 AddTransientHttpErrorPolicy 调用添加了一个 circuit breaker 策略。更远的外部请求会阻塞 30 秒,如果连续出现 5 次尝试都失败的话。Circuit breaker 策略是有状态的。所有通过这个 client 的调用都共享相同的 circuit 状态。

从 Polly registry 添加策略

 一种管理经常使用的策略的方法是只定义一次,然后使用一个 PolicyRegistry 注册它们。

在下面的代码中:

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 实例。每一个命名的 client 都会创建一个 HttpMessageHandler。工厂管理 HttpMessageHandler 实例的生命周期。

IHttpClientFactory 通过工厂集中管理 HttpMessageHandler 实例的创建来减少资源消耗。一个 HttpMessageHandler 可能会在创建新的 HttpClient 实例的时候会被复用,如果它的生命周期还没有过期的话。

集中管理处理器是可取的,因为每一个处理器都会管理自己的 HTTP 连接。创建比需要更多的处理器会导致连接延迟。一些处理器会无限期的保持连接打开,这会阻止处理器对 DNS (Domain Name System) 的改变做出反向。

默认的处理器生命周期是两分钟。每个命名的 client 的默认值都可以被覆盖:

public void ConfigureServices(IServiceCollection services)
{           
    services.AddHttpClient("extendedhandlerlifetime")
        .SetHandlerLifetime(TimeSpan.FromMinutes(5));

    // Remaining code deleted for brevity.

HttpClient 实例可以一般的当做不需要释放的 .NET 对象。释放会取消 outgoing 请求和保证给定的 HttpClient 实例在调用 Dispose 之后不能被使用。IHttpClientFactory 追踪和释放由 HttpClient 实例使用的资源。

在 IHttpClientFactory 之前,长时间保持一个单一的 HttpClient 实例存活是一种常见的模式。在迁移到 IHttpClientFactory 之后,这种模式就变的没有必要了。

IHttpClientFactory 的替代方案

在一个 DI 可用的应用程序中使用 IHttpClientFactory 可以避免:

  • 通过集中 HttpMessageHandler 实例资源耗尽问题
  • 以正常的间隔循环 HttpMessageHandler 陈旧的 DNS 问题

使用一个长时间存活的 SocketsHttpHandler 实例存在几种可替代的方法解决上面的问题:

  • 在应用程序启动时创建一个 SocketsHttpHandler 实例,在整个应用程序的生命周期中使用它
  • 基于 DNS 刷新时间配置 PooledConnectionLifetime  一个合适的值
  • 根据需要使用 new HttpClient(handler, disposeHandler: false) 创建 HttpClient 实例

上面的方法解决资源管理问题的方式和 IHttpClientFactory 解决的方法相似:

  • SocketsHttpHandler 通过 HttpClient 实例共享连接。共享阻止了套接字耗尽的问题
  • SocketsHttpHandler 通过 PooledConnectionLifetime 循环连接避免陈旧的 DNS 问题

Cooks

集中的 HttpMessageHandler 实例导致 CookieContainer 对象共享。意料之外的 CookieContainer 对象共享总是导致不正确的代码。对于需要 cookies 的应用程序,考虑:

  • 禁用自动 cookie 处理
  • 避免 IHttpClientFactory

调用 ConfigurePrimaryHttpMessageHandler 禁用自动 cookie 处理:

services.AddHttpClient("configured-disable-automatic-cookies")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new HttpClientHandler()
        {
            UseCookies = false,
        };
    });

Logging

通过 IHttpClientFactory 创建的 Clients 记录了所有请求的日志信息。在日志配置中打开合适的 Information 级别去查看默认的日志信息。更多的日志,例如记录请求头部信息,仅仅包含在追踪级别。

日志的类别被用在每一个 client 来包含 client 的名称。例如,一个名为 MyNamedClient 的 client 记录类别为 “System.Net.Http.HttpClient.MyNamedClient.LogicalHandler” 日志信息。以 LogicalHandler 结尾的信息出现在请求处理管道的外部。在请求中,消息在管道中其它处理器处理它之前就被记录。在响应中,消息在管道中其它处理程序处理之后被记录。

日志也会出现在请求处理程序内部出现。在 MyNamedClient 的例子中,那些消息被记录为 "System.Net.Http.HttpClient.MyNamedClient.ClientHandler"。对于请求,这出现在所有处理程序已经运行之后,并且在请求发送之前会立即出现。在响应中,日志在响应的状态通过处理管道传递回去之前包括到。

在管道外部和内部使能日志可以检测其它管道处理程序所做的改变。这可能包括对请求头或者响应状态码的改变。

在日志类别中包含 client 的名称使得日志可以指定命名的 clients 来过滤日志。

配置 HttpMessageHandler

对于 client 使用的内部的 HttpMessageHandler 配置控制可能是有必要的。

当添加一个 named 或者 typed 的 client 时,一个 IHttpClientBuilder 被返回。ConfigurePrimaryHttpMessageHandler 扩展方法可以用来定义一个代理。代理可以用来创建和配置由 client 使用的 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 被注册在 Generic Host's 的服务容器中
  • MyServices 从服务中创建一个 client 工厂,用来创建一个 HttpClient。HttpClient 用来获取一个网页
  • Main 创建了一个 scope 来执行服务的 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}";
            }
        }
    }
}

Header propagation middleware

Header 传播是一个 ASP.NET Core 中间件,这个中间件把 HTTP 头部从 incoming 请求传播到 outgoing HTTP Client 请求。使用 Header propagation:

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();
    });
}

client 包含 outbound 请求配置的头部:

var client = clientFactory.CreateClient("MyForwardingClient");
var response = client.GetAsync(...);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值