【.NET Core框架】HttpClient

HttpWebRequest、WebClient

在.NET中,我们有很多发送Http请求的手段,如HttpWebRequestWebClient以及HttpClient

  • HttpWebRequest
    HttpWebRequest位于System.Net命名空间下,继承自抽象类WebRequest,是.NET中最早、最原始地用于操作Http请求的类。相对来说,该类提供的方法更接近于底层,所以它的使用较为繁琐,对于开发者的水平要求是比较高的。
  • WebClient
    WebClient也位于System.Net命名空间下,它主要是对WebRequest进行了一层封装,简化了常用任务场景的使用,如文件上传、文件下载、数据上传、数据下载等,并提供了一系列事件。

不过,虽然HttpWebRequestWebClient仍然可用,但官方建议,若没有特殊要求,不要使用他俩,而应该使用HttpClient

HttpClient介绍

HttpClient用于发送http请求,默认情况下内部使用HttpWebRequest发送请求,这一行为可以通过构造函数中指定HttpMessageHandler参数来使用不同的通道
HttpClient继承自HttpMessageInvoker,从名字可以看出HttpClient是一个调用器,它是对HttpMessageHandler的一个调用,HttpClient本身不提供Http请求功能

public HttpClient(HttpMessageHandler handler){}

HttpClient相关类

HttpClient这个类本身并不会进行实际的网络请求收发处理,我们应将其理解成一个容器、一个中继者,实际的网络请求核心在HttpClientHanlder中,也就是下面图中对应的Inner Handler。

HttpClientHandler

HttpClientHandler继承自HttpMessageHandler,用于设置cookie、代理、证书、Headlers等属性

  • 是否自动处理cookie
    默认HttpClient是自动处理cookie的,即:上一个请求返回的cookie,可能会随着下次请求发送出去。
            HttpClientHandler httpClientHandler = new HttpClientHandler { 
                UseCookies=false//是否自动处理cookie
            };
            HttpClient client = new HttpClient(httpClientHandler);
  • 是否自动重定向以及最多重定向几次
    默认HttpClient自动处理重定向请求,并且最多重定向50次
HttpClientHandler httpClientHandler = new HttpClientHandler
            {
                AllowAutoRedirect = true,//是否允许重定向,默认为true
                MaxAutomaticRedirections = 50//最多重定向几次,默认50次
            };
  • 内部TCP链接池的设置
    每个url(如:http://www.baidu.com:80)最多有几个链接,默认是int.MaxValue。注意:url是不带路径及参数;
            HttpClientHandler httpClientHandler = new HttpClientHandler
            {
                MaxConnectionsPerServer=int.MaxValue, 
            };
  • 是否压缩
    默认是不压缩,如果设置开启压缩的话,http请求头中会自动加上Accept-Encoding: gzip(当然你得设置压缩选项是gzip),如果后台也支持这种压缩的话,就会把消息体压缩并在响应头中添加Content-Encoding: gzip
            HttpClientHandler httpClientHandler = new HttpClientHandler
            {
                AutomaticDecompression=System.Net.DecompressionMethods.GZip
            };
  • 超时设置
            SocketsHttpHandler socketsHttpHandler = new SocketsHttpHandler { 
               ConnectTimeout= System.Threading.Timeout.InfiniteTimeSpan//默认不限制
            };
  • 响应头数据大小限制
    http响应头最大字节数
            HttpClientHandler httpClientHandler = new HttpClientHandler
            {
                MaxResponseHeadersLength = 64,//单位是KB
            };

验证相关:PreAuthenticate、Credentials
代理相关:Proxy、UseProxy、DefaultProxyCredentials

DelegatingHandler

抽象类,DelegatingHandler继承自HttpMessageHandlerDelegatingHandler内部有一个属性InnerHandler,http请求都是通过InnerHandler发送,这个InnerHandler就是HttpClientHandler。通过继承DelegatingHandler类可实现类似HttpModule的功能

public abstract class DelegatingHandler : HttpMessageHandler
{}

HttpClient请求过程

从Request发起,经过DelegatingHandler处理后,进入InnerHandler,数据返回后再从Inner Handler 返回到Delegating Hanlder进行处理,最后返回结果。
从设计角度来讲,HttpClient库提供了强大的扩展性,使用者不需要任何继承即可完成对HttpClient的扩展(如果对设计模式熟悉,可以清楚的看出这里用到了装饰器模式)

HttpResponseMessage

HttpResponseMessageHttpClient请求的返回类型,内部包含HeadlersContentStatusCodeIsSuccessStatusCode等属性
EnsureSuccessStatusCode()方法:如果不返回200就抛出异常
其中Content属性是HttpContent类型,可转成对应类型,获取Content数据

HttpClientFactory

IHttpClientFactory可以帮我们创建所需要的HttpClient实例,我们无须关心实例的创建过程。与HttpClient一样,位于System.Net.Http命名空间下。

基础用法

services.AddHttpClient();
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}";
    }
}

jsonplaceholder.typicode.com 是一个免费提供虚假API的网站,我们可以使用它来方便测试。

命名客户端

命名化客户端,可以添加一些特定配置。您可以注册多个命名化客户端,每个客户端都可以预先配置不同的设置。

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}";
}

类型化客户端

类型化客户端的好处是无需像命名客户端那样通过传递字符串获取客户端实例,可以将同一类别的调用接口进行归类、封装

services.AddHttpClient<SSOService>();

//类型化客户端类
public class SSOService
{
    private readonly HttpClient _httpClient = null;
    public SSOService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }
    public string GetContent()
    {
        return _httpClient.GetAsync("https://www.baidu.com").Result.Content.ReadAsStringAsync().Result;
    }
}
 public async Task<ActionResult> Test([FromServices] SSOService ssoService)
 {
     var result3= ssoService.GetContent();
     return Content();
 }

借助第三方库生成的客户端

一般来说,类型化的客户端已经大大简化了我们使用HttpClient的步骤和难度,不过,我们还可以借助第三方库再次简化我们的代码:我们只需要定义要调用的服务接口,第三方库会生成代理类。
常用的第三方库有以下两个:

  • Refit
  • WebApiClientCore

可以参考:

https://www.xcode.me/code/refit-the-automatic-type-safe-rest-library
https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/http-requests?view=aspnetcore-2.1#outgoing-request-middleware

HttpClient案例

案例1:设置cookie、Headers、代理、证书等,通过设置HttpClientHandler属性实现

            HttpClientHandler httpClientHandler = new HttpClientHandler();
            httpClientHandler.CookieContainer.Add(new Cookie("id", "1"));
            //httpClientHandler.Proxy = null;
            //httpClientHandler.Credentials = null;
            HttpClient httpClient = new HttpClient(httpClientHandler);

案例2:记录HttpClient请求、响应内容日志,通过继承DelegatingHandler抽象类实现

我们自己定义了一个LoggingHandler,这个类对应Delegating Handler 是我们自定义的、装饰在Inner Handler外的Handler。
DelegatingHandler重载了SendAsync,在其内部调用了InnerHandler的SendAsync方法,如此我们便可以在实际请求发出,以及返回后进行各种统一的处理,总结起来仍是上面图中画出的,逐层调用。
必须给LoggingHandler传入HttpClientHandler,因为最终都是通过HttpClientHandler发送请求,如果不传会抛异常

public class LoggingHandler : DelegatingHandler
    {
        public LoggingHandler(HttpMessageHandler handler) : base(handler) { }
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            if (request.Content!=null)
            {
                Debug.WriteLine(await request.Content.ReadAsStringAsync());
            }
            HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
            if (response.Content!=null)
            {
                Debug.WriteLine(await response.Content.ReadAsStringAsync());
            }
            return response;
        }
    }
HttpMessageHandler handler = new HttpClientHandler();
HttpClient client = new HttpClient(new LoggingHandler(handler));

案例3:失败重试

public class RetryHandler : DelegatingHandler
    {
        private const int MAX_COUNT = 3;
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            HttpResponseMessage response = null;
            for (int i = 0; i < MAX_COUNT; i++)
            {
                response = await base.SendAsync(request, cancellationToken);
                if (response.IsSuccessStatusCode)
                {
                    return response;
                }
            }
            return response;
            
        }
    }
HttpMessageHandler handler = new HttpClientHandler();
handler = new LoggingHandler(handler);
handler = new RetryHandler(handler);
HttpClient client = new HttpClient(handler);

案例:ASP.NET Core中指定DelegatingHandler、HttpClientHandler

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddHttpClient("client1", client => { client.BaseAddress = new System.Uri("https://www.baidu.com"); })
                .AddHttpMessageHandler(() => new RetryHandler())//设置DelegatingHandler
                .ConfigurePrimaryHttpMessageHandler(() => new System.Net.Http.HttpClientHandler()
                {//设置HttpClientHandler,也就是InnerHandler
                    UseCookies = false
                });
        }

案例:允许不校验证书访问(自签名证书)

var clientHandler = new HttpClientHandler();
clientHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
HttpClient client = new HttpClient(clientHandler);

HttpClient+Consule案例

展开代码
public class ConsulDiscoveryDelegatingHandler : DelegatingHandler
{
     protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
     {
          var current = request.RequestUri;
          try
          {              //调用的服务地址里的域名(主机名)传入发现的服务名称即可
              request.RequestUri = new Uri($"{current.Scheme}://{LookupService(current.Host)}/{current.PathAndQuery}");
              return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
          }
          catch (Exception e)
          {
              throw;
          }
          finally
          {
              request.RequestUri = current;
          }
      }

      private string LookupService(string serviceName)
      {
          using (ConsulClient consulClient = new ConsulClient(config=>config.Address=new Uri("http://localhost:8500/")))
          {
              var services = _consulClient.Catalog.Service(serviceName).Result.Response;
              if (services != null && services.Any())
              {
                    //模拟负载均衡算法(随机获取一个地址)
                    int index = r.Next(services.Count());
                    var service = services.ElementAt(index);
                    return $"{service.ServiceAddress}:{service.ServicePort}");
               }
               return null;
          }
      }}

调用

public async Task<Person> GetPerson(int personId)
{
    using (HttpClient client = new HttpClient(new ConsulDiscoveryDelegatingHandler()))
    {       //调用时的域名(主机名)传入服务发现的名称即可
       var response = await client.GetAsync($"http://PersonService/Person/GetPerson?personId={personId}");
       var jsonResult = await response.Content.ReadAsStringAsync();
       return jsonResult.FromJson<Person>();
    }
}

参考:
https://blog.csdn.net/u010476739/article/details/119782562(HttpClint使用案例)

posted @ 2020-01-05 10:05  .Neterr  阅读(1475)  评论(1编辑  收藏  举报