1. 程式人生 > >ASP.NET Core 2.2 基礎知識(十二) 傳送 HTTP 請求

ASP.NET Core 2.2 基礎知識(十二) 傳送 HTTP 請求

原文: ASP.NET Core 2.2 基礎知識(十二) 傳送 HTTP 請求

可以註冊 IHttpClientFactory 並將其用於配置和建立應用中的 HttpClient 例項。 這能帶來以下好處:

  • 提供一箇中心位置,用於命名和配置邏輯 HttpClient 例項。 例如,可以註冊 github 客戶端,並將它配置為訪問 GitHub。 可以註冊一個預設客戶端用於其他用途。
  • 通過委託 HttpClient 中的處理程式整理出站中介軟體的概念,並提供適用於基於 Polly 的中介軟體的擴充套件來利用概念。
  • 管理基礎 HttpClientMessageHandler 例項的池和生存期,避免在手動管理 HttpClient 生存期時出現常見的 DNS 問題。
  • (通過 ILogger)新增可配置的記錄體驗,以處理工廠建立的客戶端傳送的所有請求。

在應用中可以通過以下多種方式使用 IHttpClientFactory

基本用法

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            services.AddHttpClient();
}

 

    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        private readonly IHttpClientFactory _clientFactory;
        public ValuesController(IHttpClientFactory clientFactory)
        {
            _clientFactory 
= clientFactory; } // GET api/values [HttpGet] public async Task<string> Get() { HttpClient client = _clientFactory.CreateClient(); //方法一: //HttpRequestMessage request = new HttpRequestMessage //{ // Method = new HttpMethod("get"), // RequestUri = new System.Uri("http://localhost:5000/api/values"), //}; //HttpResponseMessage response = await client.SendAsync(request); //string res = await response.Content.ReadAsStringAsync(); //return res; //方法二: string res = await client.GetStringAsync("http://localhost:5000/api/values"); return res; } }

 

命名客戶端

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            services.AddHttpClient("test", c =>
            {
                c.BaseAddress = new Uri("http://localhost:5000");
            });
        }

 

        public async Task<string> Get()
        {
            HttpClient client = _clientFactory.CreateClient("test");
            //註冊名叫 "test" 的客戶端時,已經指定了該客戶端的請求基地址,所以這裡不需要指定主機名了
            return await client.GetStringAsync("api/values");
        }

 

型別化客戶端

    public class TestHttpClient
    {
        public HttpClient Client { get; set; }

        public TestHttpClient(HttpClient client)
        {
            client.BaseAddress = new System.Uri("http://localhost:5000");
            Client = client;
        }

        public async Task<string> Get()
        {
            return await Client.GetStringAsync("api/values");
        }
    }

 

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            services.AddHttpClient<TestHttpClient>(c =>
            {
                //可以在這裡設定,也可以在建構函式設定.
                //c.BaseAddress = new System.Uri("http://localhost:5000");
            });
        }

 

    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        private readonly TestHttpClient _client;

        public ValuesController(TestHttpClient client)
        {
            _client = client;
        }
     
        [HttpGet]
        public async Task<string> Get()
        {
            return await _client.Get();
        }
    }

 

出站請求中介軟體

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("refuge"))
            {
                return new HttpResponseMessage(HttpStatusCode.BadRequest)
                {
                    Content = new StringContent("not found refuge")
                };
            }

            return await base.SendAsync(request, cancellationToken);
        }
    }

 

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            services.AddTransient<ValidateHeaderHandler>();//生存期必須是臨時
            services.AddHttpClient("test", c =>
                {
                    c.BaseAddress = new Uri("http://localhost:5000");
                })
                .AddHttpMessageHandler<ValidateHeaderHandler>();
        }

 

HttpClient 和生存期管理

每次對 IHttpClientFactory 呼叫 CreateClient 都會返回一個新 HttpClient 例項:

        public IEnumerable<int> Get()
        {
            //測試生存期
            for (int i = 0; i < 4; i++)
            {
                HttpClient client = i % 2 == 0
                    ? _clientFactory.CreateClient("test")
                    : _clientFactory.CreateClient();
                yield return client.GetHashCode();
            }
        }

 

 

CreateClient 方法內部會呼叫 CreateHandler 方法,後者建立 HttpMessageHandler,

原始碼如下:

    public HttpClient CreateClient(string name)
    {
      if (name == null)
        throw new ArgumentNullException(nameof (name));
      HttpClient httpClient = new HttpClient(this.CreateHandler(name), false);
      HttpClientFactoryOptions clientFactoryOptions = this._optionsMonitor.Get(name);
      for (int index = 0; index < clientFactoryOptions.HttpClientActions.Count; ++index)
        clientFactoryOptions.HttpClientActions[index](httpClient);
      return httpClient;
    }

 

    public HttpMessageHandler CreateHandler(string name)
    {
      if (name == null)
        throw new ArgumentNullException(nameof (name));
      ActiveHandlerTrackingEntry entry = this._activeHandlers.GetOrAdd(name, this._entryFactory).Value;
      this.StartHandlerEntryTimer(entry);
      return (HttpMessageHandler) entry.Handler;
    }

 

而這個 _activeHandlers 的型別是 : 

一個執行緒安全的鍵值對集合.

因此,實際上建立的 HttpMessageHandler 例項會彙集到池中.新建 HttpClient 例項時,可能會重用池中的 HttpMessageHandler 例項(如果生存期尚未到期的話). 

由於每個處理程式通常管理自己的基礎 HTTP 連線,因此需要池化處理程式.建立超出必要數量的處理程式可能會導致連線延遲. 部分處理程式還保持連線無期限地開啟,這樣可以防止處理程式對 DNS 更改作出反應.

處理程式的預設生存期為兩分鐘,可在每個命名客戶端上重寫預設值:

services.AddHttpClient("test").SetHandlerLifetime(TimeSpan.FromMinutes(5));

 

配置 HttpMessageHandler

有時候,我們需要控制客戶端使用的內部 HttpMessageHandler .

            services.AddHttpClient("test")
                .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()
                {
                    AllowAutoRedirect = false,
                });