.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&lt;HttpClientFactoryOptions&gt;(builder.Name, options =&gt;
        {
            options.HttpMessageHandlerBuilderActions.Add(b =&gt; 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);

    /// &lt;summary&gt;
    /// Gets a list of operations used to configure an &lt;see cref="HttpMessageHandlerBuilder"/&gt;.
    /// &lt;/summary&gt;
    public IList&lt;Action&lt;HttpMessageHandlerBuilder&gt;&gt; HttpMessageHandlerBuilderActions { get; } = new List&lt;Action&lt;HttpMessageHandlerBuilder&gt;&gt;();

    /// &lt;summary&gt;
    /// Gets a list of operations used to configure an &lt;see cref="HttpClient"/&gt;.
    /// &lt;/summary&gt;
    public IList&lt;Action&lt;HttpClient&gt;&gt; HttpClientActions { get; } = new List&lt;Action&lt;HttpClient&gt;&gt;();

    /// &lt;summary&gt;
    /// Gets or sets the length of time that a &lt;see cref="HttpMessageHandler"/&gt; 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 &lt;see cref="Timeout.InfiniteTimeSpan"/&gt; to disable handler expiry.
    /// &lt;/summary&gt;
    /// &lt;remarks&gt;
    /// &lt;para&gt;
    /// The default implementation of &lt;see cref="IHttpClientFactory"/&gt; will pool the &lt;see cref="HttpMessageHandler"/&gt;
    /// 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.
    /// &lt;/para&gt;
    /// &lt;para&gt;
    /// 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 &lt;see cref="HandlerLifetime"/&gt; should be
    /// chosen with an understanding of the application's requirement to respond to changes in the network environment.
    /// &lt;/para&gt;
    /// &lt;para&gt;
    /// 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
    /// &lt;see cref="HttpClient"/&gt; instances will prevent the underlying &lt;see cref="HttpMessageHandler"/&gt; from being
    /// disposed until all references are garbage-collected.
    /// &lt;/para&gt;
    /// &lt;/remarks&gt;
    public TimeSpan HandlerLifetime
    {
        get =&gt; _handlerLifetime;
        set
        {
            if (value != Timeout.InfiniteTimeSpan &amp;&amp; value &lt; MinimumHandlerLifetime)
            {
                throw new ArgumentException(Resources.HandlerLifetime_InvalidValue, nameof(value));
            }

            _handlerLifetime = value;
        }
    }

    /// &lt;summary&gt;
    /// The &lt;see cref="Func{T, R}"/&gt; which determines whether to redact the HTTP header value before logging.
    /// &lt;/summary&gt;
    public Func&lt;string, bool&gt; ShouldRedactHeaderValue { get; set; } = (header) =&gt; false;

    /// &lt;summary&gt;
    /// &lt;para&gt;
    /// Gets or sets a value that determines whether the &lt;see cref="IHttpClientFactory"/&gt; will
    /// create a dependency injection scope when building an &lt;see cref="HttpMessageHandler"/&gt;.
    /// If &lt;c&gt;false&lt;/c&gt; (default), a scope will be created, otherwise a scope will not be created.
    /// &lt;/para&gt;
    /// &lt;para&gt;
    /// This option is provided for compatibility with existing applications. It is recommended
    /// to use the default setting for new applications.
    /// &lt;/para&gt;
    /// &lt;/summary&gt;
    /// &lt;remarks&gt;
    /// &lt;para&gt;
    /// The &lt;see cref="IHttpClientFactory"/&gt; will (by default) create a dependency injection scope
    /// each time it creates an &lt;see cref="HttpMessageHandler"/&gt;. The created scope has the same
    /// lifetime as the message handler, and will be disposed when the message handler is disposed.
    /// &lt;/para&gt;
    /// &lt;para&gt;
    /// When operations that are part of &lt;see cref="HttpMessageHandlerBuilderActions"/&gt; are executed
    /// they will be provided with the scoped &lt;see cref="IServiceProvider"/&gt; via 
    /// &lt;see cref="HttpMessageHandlerBuilder.Services"/&gt;. This includes retrieving a message handler
    /// from dependency injection, such as one registered using 
    /// &lt;see cref="HttpClientBuilderExtensions.AddHttpMessageHandler{THandler}(IHttpClientBuilder)"/&gt;.
    /// &lt;/para&gt;
    /// &lt;/remarks&gt;
    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; }
    /// &lt;summary&gt;
    /// Gets or sets the primary &lt;see cref="HttpMessageHandler"/&gt;.
    /// &lt;/summary&gt;
    public abstract HttpMessageHandler PrimaryHandler { get; set; }

    /// &lt;summary&gt;
    /// Gets a list of additional &lt;see cref="DelegatingHandler"/&gt; instances used to configure an
    /// &lt;see cref="HttpClient"/&gt; pipeline.
    /// &lt;/summary&gt;
    public abstract IList&lt;DelegatingHandler&gt; AdditionalHandlers { get; }

    /// &lt;summary&gt;
    /// Gets an &lt;see cref="IServiceProvider"/&gt; which can be used to resolve services
    /// from the dependency injection container.
    /// &lt;/summary&gt;
    /// &lt;remarks&gt;
    /// This property is sensitive to the value of 
    /// &lt;see cref="HttpClientFactoryOptions.SuppressHandlerScope"/&gt;. If &lt;c&gt;true&lt;/c&gt; this
    /// property will be a reference to the application's root service provider. If &lt;c&gt;false&lt;/c&gt;
    /// (default) this will be a reference to a scoped service provider that has the same
    /// lifetime as the handler being created.
    /// &lt;/remarks&gt;
    public virtual IServiceProvider Services { get; }

    /// &lt;summary&gt;
    /// Creates an &lt;see cref="HttpMessageHandler"/&gt;.
    /// &lt;/summary&gt;
    /// &lt;returns&gt;
    /// An &lt;see cref="HttpMessageHandler"/&gt; built from the &lt;see cref="PrimaryHandler"/&gt; and
    /// &lt;see cref="AdditionalHandlers"/&gt;.
    /// &lt;/returns&gt;
    public abstract HttpMessageHandler Build();

    protected internal static HttpMessageHandler CreateHandlerPipeline(HttpMessageHandler primaryHandler, IEnumerable&lt;DelegatingHandler&gt; 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&lt;DelegatingHandler&gt; ?? additionalHandlers.ToArray();

        var next = primaryHandler;
        for (var i = additionalHandlersList.Count - 1; i &gt;= 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&lt;HttpClientFactoryOptions&gt;(builder.Name, options =&gt;
        {
            options.HttpMessageHandlerBuilderActions.Add(b =&gt; 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 =&gt; _name;
        set
        {
            if (value == null)
            {
                throw new ArgumentNullException(nameof(value));
            }

            _name = value;
        }
    }

    public override HttpMessageHandler PrimaryHandler { get; set; } = new HttpClientHandler();

    public override IList&lt;DelegatingHandler&gt; AdditionalHandlers { get; } = new List&lt;DelegatingHandler&gt;();

    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&lt;HttpResponseMessage&gt; 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&lt;HttpResponseMessage&gt; 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&lt;HttpResponseMessage&gt; 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 之前或者之后做一些自己的验证等相关业务,大家只需要理解它们的用途,自然知道它的强大作用,今天就分享到这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值