• 深入理解HttpClient


    介绍:

    HttpClient用于发送http请求,默认情况下内部使用HttpWebRequest发送请求,这一行为可以通过构造函数中指定HttpMessageHandler参数来使用不同的通道

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

    HttpClient构造函数可以接受一个HttpMessageHandler类型的参数

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

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

    HttpClient继承自HttpMessageInvoker,从名字可以看出HttpClient是一个调用器,它是对HttpMessageHandler的一个调用,HttpClient本身不提供Http请求功能

    HttpClient请求过程:

    从Request发起,经过DelegatingHanlder处理后,进入InnerHandler,数据返回后再从Inner Handler 返回到Delegating Hanlder进行处理,最后返回结果。

    从设计角度来讲,HttpClient库提供了强大的扩展性,使用者不需要任何继承即可完成对HttpClient的扩展(如果对设计模式熟悉,可以清楚的看出这里用到了装饰器模式)

    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抽象类实现

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

    代码说明:

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

    2、我们自己定义了一个LoggingHandler,这个类对应Delegating Handler 是我们自定义的、装饰在Inner Handler外的Handler。

    3、DelegatingHandler重载了SendAsync,在其内部调用了InnerHandler的SendAsync方法,如此我们便可以在实际请求发出,以及返回后进行各种统一的处理,总结起来仍是上面图中画出的,逐层调用。

    4、HttpClientHandler、DelegatingHandler都是继承自HttpMessageHandler

    5、必须给LoggingHandler传入HttpClientHandler,因为最终都是通过HttpClientHandler发送请求,如果不传会抛异常

    HttpClientHandler:

    public class HttpClientHandler : HttpMessageHandler
    {}

    DelegatingHandler:

    public abstract class DelegatingHandler : HttpMessageHandler
    {}

    案例3:重试

    封装一个RetryHandler,目的是失败重试

    RetryHandler:

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

    案例4: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
                    });
            }

    HttpResponseMessage:

    HttpClient请求的返回类型,内部包含Headlers、Content、StatusCode、IsSuccessStatusCode等属性

    其中Content属性是HttpContent类型,可转成对应类型,获取Content数据

    性能隐患:

    HttpClient有一个性能隐患,当要发送大量的http请求,如果频繁的创建HttpClient对象,会频繁建立TCP连接,连接不够用时会出现timeout,所以应该避免频繁创建HttpClient对象,如下使用静态变量+长连接。

    private static readonly HttpClient _httpClient;
    
            static ApiLogger()
            {
                _httpClient = new HttpClient();
                _httpClient.Timeout = new TimeSpan(0, 0, 10);
                _httpClient.DefaultRequestHeaders.Connection.Add("keep-alive");
            }

    上面这种方式有一个缺点,会缓存Dns,所以.net core中应该使用HttpClientFactory创建HttpClient

    HttpClient扩展方法:

    public static class HttpClientEx 
        {
            //GZIP压缩
            //var handler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip }
            //HttpClient client = new HttpClient(handler);
    
            /// <summary>
            /// http://www.it165.net/pro/html/201306/6052.html
            /// GET获取HTTP信息
            /// </summary>
            /// <param name="url"></param>
            /// <returns></returns>
            public static string Get(this HttpClient client,string url)
            {
                return client.GetStringAsync(url).Result;
            }
            /// <summary>
            /// POST空的HTTP信息
            /// http://www.cnblogs.com/xishuai/p/aspnet_mvc_web_api_httpclient_json_frombody_post.html
            /// </summary>
            /// <param name="url"></param>
            /// <returns></returns>
            public static string Post(this HttpClient client,string url)
            {
                string responseText = null;
                using (StringContent content = new StringContent(string.Empty))
                {
                    content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
                    using (HttpResponseMessage responseMessage = client.PostAsync(url, content).Result)
                    {
                        responseMessage.EnsureSuccessStatusCode();//确保请求成功
                        responseText = responseMessage.Content.ReadAsStringAsync().Result;
    
                    }
                }
                return responseText;
            }
            /// <summary>
            /// http://www.haogongju.net/art/1642652
            /// POST方式发送信息(x-www-form-urlencoded字典格式)
            /// </summary>
            /// <param name="url"></param>
            /// <param name="dict">x-www-form-urlencoded字典格式</param>
            /// <param name="isException">false表示抛出异常,true表示返回异常错误信息</param>
            /// <returns></returns>
            public static string Post(this HttpClient client,string url, Dictionary<string, string> dict)
            {
                string responseText = null;
                using (FormUrlEncodedContent content = new FormUrlEncodedContent(dict))
                {
                    //content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
                    using (HttpResponseMessage responseMessage = client.PostAsync(url, content).Result)
                    {
                        responseMessage.EnsureSuccessStatusCode();
                        responseText = responseMessage.Content.ReadAsStringAsync().Result;
    
                    }
                }
                return responseText;
            }
            /// <summary>
            /// http://www.cnblogs.com/xishuai/p/aspnet_mvc_web_api_httpclient_json_frombody_post.html
            /// POST方式发送信息(raw文本格式)
            /// </summary>
            /// <param name="url"></param>
            /// <param name="raw">raw文本格式</param>
            /// <param name="isException">false表示抛出异常,true表示返回异常错误信息</param>
            /// <returns></returns>
            public static string Post(this HttpClient client,string url, string raw)
            {
                string responseText = null;
                using (StringContent content = new StringContent(raw))
                {
                    content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                    using (HttpResponseMessage responseMessage = client.PostAsync(url, content).Result)
                    {
                        responseMessage.EnsureSuccessStatusCode();
                        responseText = responseMessage.Content.ReadAsStringAsync().Result;
    
                    }
                }
                return responseText;
            }
    
            
    
        }

    在.NET Core中,如果使用HttpClientFactory创建HttpClient,如何添加HttpMessageHandler?

    需要自己去实现接口IHttpMessageHandlerBuilderFilter,看下一篇

    参考:

    https://www.cnblogs.com/Herzog3/p/6128822.html

    https://www.cnblogs.com/Leo_wl/p/3439512.html

    https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/http-message-handlers

    https://blog.csdn.net/weixin_30236595/article/details/101066107

    ...

  • 相关阅读:
    深入理解 ProtoBuf 原理与工程实践(概述)
    高性能缓存 Caffeine 原理及实战
    Java 多线程上下文传递在复杂场景下的实践
    SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
    MySQL 5.6.35 索引优化导致的死锁案例解析
    gitlab安装升级(大版本跨度9.4.5----13.2.1)
    mysql 查看表的索引
    python安装mysql库 ,MySQL-python
    Alpine包管理工具apk使用介绍
    docker容器添加hosts
  • 原文地址:https://www.cnblogs.com/fanfan-90/p/12409101.html
Copyright © 2020-2023  润新知