zoukankan      html  css  js  c++  java
  • .net core HttpClient 使用之消息管道解析(二)

    一、前言

    前面分享了 .net core HttpClient 使用之掉坑解析(一),今天来分享自定义消息处理HttpMessageHandlerPrimaryHttpMessageHandler 的使用场景和区别

    二、源代码阅读

    2.1 核心消息管道模型图

    先贴上一张核心MessageHandler 管道模型的流程图,图如下:

    HttpClient 中的HttpMessageHandler 负责主要核心的业务,HttpMessageHandler 是由MessageHandler 链表结构组成,形成一个消息管道模式;具体我们一起来看看源代码

    2.2 Demo代码演示

    再阅读源代码的时候我们先来看下下面注入HttpClient 的Demo 代码,代码如下:

    services.AddHttpClient("test")
            .ConfigurePrimaryHttpMessageHandler(provider =>
            {
                return new PrimaryHttpMessageHandler(provider);
            })
            .AddHttpMessageHandler(provider =>
            {
                return new LogHttpMessageHandler(provider);
            })
            .AddHttpMessageHandler(provider =>
            {
               return new Log2HttpMessageHandler(provider);
            });
    

    上面代码中有两个核心扩展方法,分别是ConfigurePrimaryHttpMessageHandlerAddHttpMessageHandler,这两个方法大家可能会有疑问是做什么的呢?
    不错,这两个方法就是扩展注册自定义的HttpMessageHandler 如果不注册,会有默认的HttpMessageHandler,接下来我们分别来看下提供的扩展方法,如下图:

    图中提供了一系列的AddHttpMessageHandler 扩展方法和ConfigurePrimaryHttpMessageHandler的扩展方法。

    2.3 AddHttpMessageHandler

    我们来看看HttpClientBuilderExtensions中的其中一个AddHttpMessageHandler扩展方法,代码如下:

            /// <summary>
            /// Adds a delegate that will be used to create an additional message handler for a named <see cref="HttpClient"/>.
            /// </summary>
            /// <param name="builder">The <see cref="IHttpClientBuilder"/>.</param>
            /// <param name="configureHandler">A delegate that is used to create a <see cref="DelegatingHandler"/>.</param>
            /// <returns>An <see cref="IHttpClientBuilder"/> that can be used to configure the client.</returns>
            /// <remarks>
            /// The <see paramref="configureHandler"/> delegate should return a new instance of the message handler each time it
            /// is invoked.
            /// </remarks>
            public static IHttpClientBuilder AddHttpMessageHandler(this IHttpClientBuilder builder, Func<DelegatingHandler> configureHandler)
            {
                if (builder == null)
                {
                    throw new ArgumentNullException(nameof(builder));
                }
    
                if (configureHandler == null)
                {
                    throw new ArgumentNullException(nameof(configureHandler));
                }
    
                builder.Services.Configure<HttpClientFactoryOptions>(builder.Name, options =>
                {
                    options.HttpMessageHandlerBuilderActions.Add(b => b.AdditionalHandlers.Add(configureHandler()));
                });
    
                return builder;
            }
    

    代码中把自定义的DelegatingHandler 方法添加到HttpMessageHandlerBuilderActions中,我们再来看看HttpClientFactoryOptions对象源代码,如下:

     /// <summary>
        /// An options class for configuring the default <see cref="IHttpClientFactory"/>.
        /// </summary>
        public class HttpClientFactoryOptions
        {
            // Establishing a minimum lifetime helps us avoid some possible destructive cases.
            //
            // IMPORTANT: This is used in a resource string. Update the resource if this changes.
            internal readonly static TimeSpan MinimumHandlerLifetime = TimeSpan.FromSeconds(1);
    
            private TimeSpan _handlerLifetime = TimeSpan.FromMinutes(2);
    
            /// <summary>
            /// Gets a list of operations used to configure an <see cref="HttpMessageHandlerBuilder"/>.
            /// </summary>
            public IList<Action<HttpMessageHandlerBuilder>> HttpMessageHandlerBuilderActions { get; } = new List<Action<HttpMessageHandlerBuilder>>();
    
            /// <summary>
            /// Gets a list of operations used to configure an <see cref="HttpClient"/>.
            /// </summary>
            public IList<Action<HttpClient>> HttpClientActions { get; } = new List<Action<HttpClient>>();
    
            /// <summary>
            /// Gets or sets the length of time that a <see cref="HttpMessageHandler"/> instance can be reused. Each named 
            /// client can have its own configured handler lifetime value. The default value of this property is two minutes.
            /// Set the lifetime to <see cref="Timeout.InfiniteTimeSpan"/> to disable handler expiry.
            /// </summary>
            /// <remarks>
            /// <para>
            /// The default implementation of <see cref="IHttpClientFactory"/> will pool the <see cref="HttpMessageHandler"/>
            /// instances created by the factory to reduce resource consumption. This setting configures the amount of time
            /// a handler can be pooled before it is scheduled for removal from the pool and disposal.
            /// </para>
            /// <para>
            /// Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections; creating
            /// more handlers than necessary can result in connection delays. Some handlers also keep connections open indefinitely
            /// which can prevent the handler from reacting to DNS changes. The value of <see cref="HandlerLifetime"/> should be
            /// chosen with an understanding of the application's requirement to respond to changes in the network environment.
            /// </para>
            /// <para>
            /// Expiry of a handler will not immediately dispose the handler. An expired handler is placed in a separate pool 
            /// which is processed at intervals to dispose handlers only when they become unreachable. Using long-lived
            /// <see cref="HttpClient"/> instances will prevent the underlying <see cref="HttpMessageHandler"/> from being
            /// disposed until all references are garbage-collected.
            /// </para>
            /// </remarks>
            public TimeSpan HandlerLifetime
            {
                get => _handlerLifetime;
                set
                {
                    if (value != Timeout.InfiniteTimeSpan && value < MinimumHandlerLifetime)
                    {
                        throw new ArgumentException(Resources.HandlerLifetime_InvalidValue, nameof(value));
                    }
    
                    _handlerLifetime = value;
                }
            }
    
            /// <summary>
            /// The <see cref="Func{T, R}"/> which determines whether to redact the HTTP header value before logging.
            /// </summary>
            public Func<string, bool> ShouldRedactHeaderValue { get; set; } = (header) => false;
    
            /// <summary>
            /// <para>
            /// Gets or sets a value that determines whether the <see cref="IHttpClientFactory"/> will
            /// create a dependency injection scope when building an <see cref="HttpMessageHandler"/>.
            /// If <c>false</c> (default), a scope will be created, otherwise a scope will not be created.
            /// </para>
            /// <para>
            /// This option is provided for compatibility with existing applications. It is recommended
            /// to use the default setting for new applications.
            /// </para>
            /// </summary>
            /// <remarks>
            /// <para>
            /// The <see cref="IHttpClientFactory"/> will (by default) create a dependency injection scope
            /// each time it creates an <see cref="HttpMessageHandler"/>. The created scope has the same
            /// lifetime as the message handler, and will be disposed when the message handler is disposed.
            /// </para>
            /// <para>
            /// When operations that are part of <see cref="HttpMessageHandlerBuilderActions"/> are executed
            /// they will be provided with the scoped <see cref="IServiceProvider"/> via 
            /// <see cref="HttpMessageHandlerBuilder.Services"/>. This includes retrieving a message handler
            /// from dependency injection, such as one registered using 
            /// <see cref="HttpClientBuilderExtensions.AddHttpMessageHandler{THandler}(IHttpClientBuilder)"/>.
            /// </para>
            /// </remarks>
            public bool SuppressHandlerScope { get; set; }
        }
    

    源代码中有如下核心List:

     public IList<Action<HttpMessageHandlerBuilder>> HttpMessageHandlerBuilderActions { get; } = new List<Action<HttpMessageHandlerBuilder>>();
    

    提供了HttpMessageHandlerBuilder HttpMessageHandler 的构造器列表对象,故,通过AddHttpMessageHandler可以添加一系列的消息构造器方法对象
    我们再来看看这个消息构造器类,核心部分,代码如下:

    public abstract class HttpMessageHandlerBuilder
        {
            /// <summary>
            /// Gets or sets the name of the <see cref="HttpClient"/> being created.
            /// </summary>
            /// <remarks>
            /// The <see cref="Name"/> is set by the <see cref="IHttpClientFactory"/> infrastructure
            /// and is public for unit testing purposes only. Setting the <see cref="Name"/> outside of
            /// testing scenarios may have unpredictable results.
            /// </remarks>
            public abstract string Name { get; set; }
    
            /// <summary>
            /// Gets or sets the primary <see cref="HttpMessageHandler"/>.
            /// </summary>
            public abstract HttpMessageHandler PrimaryHandler { get; set; }
    
            /// <summary>
            /// Gets a list of additional <see cref="DelegatingHandler"/> instances used to configure an
            /// <see cref="HttpClient"/> pipeline.
            /// </summary>
            public abstract IList<DelegatingHandler> AdditionalHandlers { get; }
    
            /// <summary>
            /// Gets an <see cref="IServiceProvider"/> which can be used to resolve services
            /// from the dependency injection container.
            /// </summary>
            /// <remarks>
            /// This property is sensitive to the value of 
            /// <see cref="HttpClientFactoryOptions.SuppressHandlerScope"/>. If <c>true</c> this
            /// property will be a reference to the application's root service provider. If <c>false</c>
            /// (default) this will be a reference to a scoped service provider that has the same
            /// lifetime as the handler being created.
            /// </remarks>
            public virtual IServiceProvider Services { get; }
    
            /// <summary>
            /// Creates an <see cref="HttpMessageHandler"/>.
            /// </summary>
            /// <returns>
            /// An <see cref="HttpMessageHandler"/> built from the <see cref="PrimaryHandler"/> and
            /// <see cref="AdditionalHandlers"/>.
            /// </returns>
            public abstract HttpMessageHandler Build();
    
            protected internal static HttpMessageHandler CreateHandlerPipeline(HttpMessageHandler primaryHandler, IEnumerable<DelegatingHandler> additionalHandlers)
            {
                // This is similar to https://github.com/aspnet/AspNetWebStack/blob/master/src/System.Net.Http.Formatting/HttpClientFactory.cs#L58
                // but we don't want to take that package as a dependency.
    
                if (primaryHandler == null)
                {
                    throw new ArgumentNullException(nameof(primaryHandler));
                }
    
                if (additionalHandlers == null)
                {
                    throw new ArgumentNullException(nameof(additionalHandlers));
                }
    
                var additionalHandlersList = additionalHandlers as IReadOnlyList<DelegatingHandler> ?? additionalHandlers.ToArray();
    
                var next = primaryHandler;
                for (var i = additionalHandlersList.Count - 1; i >= 0; i--)
                {
                    var handler = additionalHandlersList[i];
                    if (handler == null)
                    {
                        var message = Resources.FormatHttpMessageHandlerBuilder_AdditionalHandlerIsNull(nameof(additionalHandlers));
                        throw new InvalidOperationException(message);
                    }
    
                    // Checking for this allows us to catch cases where someone has tried to re-use a handler. That really won't
                    // work the way you want and it can be tricky for callers to figure out.
                    if (handler.InnerHandler != null)
                    {
                        var message = Resources.FormatHttpMessageHandlerBuilder_AdditionHandlerIsInvalid(
                            nameof(DelegatingHandler.InnerHandler),
                            nameof(DelegatingHandler),
                            nameof(HttpMessageHandlerBuilder),
                            Environment.NewLine,
                            handler);
                        throw new InvalidOperationException(message);
                    }
    
                    handler.InnerHandler = next;
                    next = handler;
                }
    
                return next;
            }
        }
    

    HttpMessageHandlerBuilder构造器中有两个核心属性PrimaryHandlerAdditionalHandlers ,细心的同学可以发现AdditionalHandlers是一个IList<DelegatingHandler>列表,也就是说可以HttpClient 可以添加多个DelegatingHandler 即多个HttpMessageHandler 消息处理Handler 但是只能有一个PrimaryHandler Handler

    同时HttpMessageHandlerBuilder提供了一个抽象的Build方法,还有一个CreateHandlerPipeline 方法,这个方法主要是把IList<DelegatingHandler>PrimaryHandler 构造成一个MessageHandler 链表结构(通过DelegatingHandlerInnerHandler属性进行连接起来)

    2.4 ConfigurePrimaryHttpMessageHandler

     public static IHttpClientBuilder ConfigurePrimaryHttpMessageHandler(this IHttpClientBuilder builder, Func<HttpMessageHandler> configureHandler)
            {
                if (builder == null)
                {
                    throw new ArgumentNullException(nameof(builder));
                }
    
                if (configureHandler == null)
                {
                    throw new ArgumentNullException(nameof(configureHandler));
                }
    
                builder.Services.Configure<HttpClientFactoryOptions>(builder.Name, options =>
                {
                    options.HttpMessageHandlerBuilderActions.Add(b => b.PrimaryHandler = configureHandler());
                });
    
                return builder;
            }
    

    通过上面的HttpMessageHandlerBuilder 源代码分析ConfigurePrimaryHttpMessageHandler 方法主要是给Builder 中添加PrimaryHandler消息Handler

    2.5 DefaultHttpMessageHandlerBuilder

    我们知道在services.AddHttpClient() 方法中会注册默认的DefaultHttpMessageHandlerBuilder 消息构造器方法,它继承DefaultHttpMessageHandlerBuilder,那我们来看看它的源代码

    internal class DefaultHttpMessageHandlerBuilder : HttpMessageHandlerBuilder
        {
            public DefaultHttpMessageHandlerBuilder(IServiceProvider services)
            {
                Services = services;
            }
    
            private string _name;
    
            public override string Name
            {
                get => _name;
                set
                {
                    if (value == null)
                    {
                        throw new ArgumentNullException(nameof(value));
                    }
    
                    _name = value;
                }
            }
    
            public override HttpMessageHandler PrimaryHandler { get; set; } = new HttpClientHandler();
    
            public override IList<DelegatingHandler> AdditionalHandlers { get; } = new List<DelegatingHandler>();
    
            public override IServiceProvider Services { get; }
    
            public override HttpMessageHandler Build()
            {
                if (PrimaryHandler == null)
                {
                    var message = Resources.FormatHttpMessageHandlerBuilder_PrimaryHandlerIsNull(nameof(PrimaryHandler));
                    throw new InvalidOperationException(message);
                }
                
                return CreateHandlerPipeline(PrimaryHandler, AdditionalHandlers);
            }
    

    代码中Build 会去调用HttpMessageHandlerBuilder 的CreateHandlerPipeline方法把HttpMessageHandler 构建成一个类似于链表的结构。
    到这里源代码已经分析完了,接下来我们来演示一个Demo,来证明上面的核心HttpMessageHandler 流程走向图

    三、Demo演示证明

    我们继续来看上面我的Demo代码:

    services.AddHttpClient("test")
            .ConfigurePrimaryHttpMessageHandler(provider =>
            {
                return new PrimaryHttpMessageHandler(provider);
            })
            .AddHttpMessageHandler(provider =>
            {
                return new LogHttpMessageHandler(provider);
            })
            .AddHttpMessageHandler(provider =>
            {
               return new Log2HttpMessageHandler(provider);
            });
    

    代码中自定义了两个HttpMessageHandler和一个PrimaryHttpMessageHandler
    我们再来分别看看Log2HttpMessageHandlerLogHttpMessageHandlerPrimaryHttpMessageHandler 代码,代码很简单就是SendAsync前后输出了Log信息,代码如下:
    自定义的PrimaryHttpMessageHandler 代码如下:

    public class PrimaryHttpMessageHandler: DelegatingHandler
        {
            private IServiceProvider _provider;
    
            public PrimaryHttpMessageHandler(IServiceProvider provider)
            {
                _provider = provider;
                InnerHandler = new HttpClientHandler();
            }
    
            protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                System.Console.WriteLine("PrimaryHttpMessageHandler Start Log");
    
                var response= await base.SendAsync(request, cancellationToken);
                System.Console.WriteLine("PrimaryHttpMessageHandler End Log");
                return response;
            }
        }
    

    Log2HttpMessageHandler 代码如下:

     public class Log2HttpMessageHandler : DelegatingHandler
        {
            private IServiceProvider _provider;
    
            public Log2HttpMessageHandler(IServiceProvider provider)
            {
                _provider = provider;
                //InnerHandler = new HttpClientHandler();
            }
    
            protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                System.Console.WriteLine("LogHttpMessageHandler2 Start Log");
                var response=await base.SendAsync(request, cancellationToken);
                System.Console.WriteLine("LogHttpMessageHandler2 End Log");
    
                return response;
            }
        }
    

    LogHttpMessageHandler代码如下:

     public class LogHttpMessageHandler : DelegatingHandler
      {
            private IServiceProvider _provider;
    
            public LogHttpMessageHandler(IServiceProvider provider)
            {
                _provider = provider;
                //InnerHandler = new HttpClientHandler();
            }
    
            protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                System.Console.WriteLine("LogHttpMessageHandler Start Log");
                var response=await base.SendAsync(request, cancellationToken);
                System.Console.WriteLine("LogHttpMessageHandler End Log");
                return response;
            }
        }
    

    三个自定义Handler 代码已经完成,我们继续添加调用代码,如下:

            /// <summary>
            /// 
            /// </summary>
            /// <param name="url"></param>
            /// <returns></returns>
            public async Task<string> GetBaiduAsync(string url)
            {
                var client = _clientFactory.CreateClient("test");
                var result = await client.GetStringAsync(url);
                return result;
            }
    

    现在我们运行访问接口,运行后的控制台Log 如下图:

    看到输出结果,大家有没有发现跟Asp.net core 中的中间件管道的运行图一样。

    四、总结

    HttpClientHttpMessageHandler可以自定义多个,但是只能有一个PrimaryHttpMessageHandler如果添加多个只会被最后面添加的给覆盖;添加的一系列Handler 构成一个链式管道模型,并且PrimaryHttpMessageHandler 主的消息Handler 是在管道的最外层,也就是管道模型中的最后一道Handler。
    使用场景:我们可以通过自定义的MessageHandler 来动态加载请求证书,通过数据库的一些信息,在自定义的Handler 中加载注入对应的证书,这样可以起到动态加载支付证书作用,同时可以SendAsync 之前或者之后做一些自己的验证等相关业务,大家只需要理解它们的用途,自然知道它的强大作用,今天就分享到这里

  • 相关阅读:
    Python之路(第二十篇) subprocess模块
    Python之路(第十九篇)hashlib模块
    Python之路(第十八篇)shutil 模块、zipfile模块、configparser模块
    Python之路(第十六篇)xml模块、datetime模块
    Java性能优化之编程技巧总结
    Java消息中间件入门笔记
    Java线程池实现原理与技术(ThreadPoolExecutor、Executors)
    Java系统高并发之Redis后端缓存优化
    Java实现一个简单的加密解密方法
    Java实现动态修改Jar包内文件内容
  • 原文地址:https://www.cnblogs.com/jlion/p/12897923.html
Copyright © 2011-2022 走看看