.Net Core HttpClient处理响应压缩详细

目录
  • 一、使用方式
  • 二、源码探究

前言:

ASP.NET Core服务端处理关于响应压缩的请求,服务端的主要工作就是根据Content-Encoding头信息判断采用哪种方式压缩并返回。之前在群里有人问道过,现在的网络带宽这么高了还有必要在服务端针对请求进行压缩吗?确实,如今分布式和负载均衡技术这么成熟,很多需要处理高并发大数据的场景都可以通过增加服务器节点来进行。但是,在资源受限的情况下,或者是还没必要为了某一个点去增加新的服务器节点的时候,我们还是要采用一些程序本身的常规处理手段来进行处理。笔者个人认为响应压缩的使用场景是这样的,在带宽压力比较紧张的情况,且CPU资源比较充足的情况下,使用响应压缩整体效果还是比较明显的。
    有压缩就有解压,而解压的工作就是在请求客户端处理的。比如浏览器,这是我们最常用的Http客户端,许多浏览器都是默认在我们发出请求的时候(比如我们浏览网页的时候)在Request Head中添加Content-Encoding,然后根据响应信息处理相关解压。这些都源于浏览器已经内置了关于请求压缩和解压的机制。类似的还有许多,比如常用的代理抓包工具Filder也是内置这种机制的。只不过需要手动去处理,但实现方式都是一样的。有时候我们在自己写程序的过程中也需要使用这种机制,在传统的.Net HttpWebRequest类库中,并没有这种机制,后来版本中加入了HttpClient,有自带的机制可以处理这种操作,

一、使用方式

首先我们来看一下直接在HttpClient中如何处理响应压缩

//自定义HttpClientHandler实例
HttpClientHandler httpClientHandler = new HttpClientHandler
{
    AutomaticDecompression = DecompressionMethods.GZip
};
//使用传递自定义HttpClientHandler实例的构造函数
using (HttpClient client = new HttpClient(httpClientHandler))
{
    var response = await client.GetAsync($"http://MyDemo/Home/GetPerson?userId={userId}");
}

这个操作还是非常简单的,我们操作的并不是HttpClient的属性而是HttpClientHandler中的属性,我们在之前的文章[.NET Core HttpClient源码探究]中曾探讨过,HttpClient的本质其实就是HttpMessageHandler,而HttpClient真正使用到的是HttpMessageHandler最重要的一个子类HttpClientHandler,所有的请求操作都是通过HttpMessageHandler进行的。我们可以看到AutomaticDecompression接受的是DecompressionMethods枚举,既然是枚举就说明包含了不止一个值,接下来我们查看DecompressionMethods中的源码

[Flags]
public enum DecompressionMethods
{
    // 使用所有压缩解压缩算法。
    All = -1,
    // 不使用解压
    None = 0x0,
    // 使用gzip解压算法
    GZip = 0x1,
    // 使用deflate解压算法
    Deflate = 0x2,
    // 使用Brotli解压算法
    Brotli = 0x4
}

该枚举默认都是针对常用输出解压算法,接下来我们看一下在HttpClientFactory中如何处理响应压缩。在之前的文章[.NET Core HttpClientFactory+Consul实现服务发现]中我们曾探讨过HttpClientFactory的大致工作方式默认PrimaryHandler传递的就是HttpClientHandler实例,而且在我们注册HttpClientFactory的时候是可以通过ConfigurePrimaryHttpMessageHandler自定义PrimaryHandler的默认值,接下来我们具体代码实现

services.AddHttpClient("mydemo", c =>
{
    c.BaseAddress = new Uri("http://MyDemo/");
}).ConfigurePrimaryHttpMessageHandler(provider=> new HttpClientHandler
{
    AutomaticDecompression = DecompressionMethods.GZip
});

其实在注册HttpClientFactory的时候还可以使用自定义的HttpClient,具体的使用方式是这样的

services.AddHttpClient("mydemo", c =>
{
    c.BaseAddress = new Uri("http://MyDemo/");
}).ConfigureHttpClient(provider => new HttpClient(new HttpClientHandler
{
    AutomaticDecompression = DecompressionMethods.GZip
}));

HttpClient确实帮我们做了好多事情,只需要简单的配置一下就开启了针对响应压缩的处理。这更勾起了我们对HttpClient的探讨,接下来我们就通过源码的方式查看它是如何发起可响应压缩请求,并解压响应结果的。

二、源码探究

通过上面的使用方式我们得知,无论使用哪种形式,最终都是针对HttpClientHandler做配置操作,接下来我们查看HttpClientHandler类中AutomaticDecompression属性的代码

public DecompressionMethods AutomaticDecompression
{
    get => _underlyingHandler.AutomaticDecompression;
    set => _underlyingHandler.AutomaticDecompression = value;
}

它本身的值操作来自_underlyingHandler这个对象,也就是说读取和设置都是在操作_underlyingHandler.AutomaticDecompression,我们查找到_underlyingHandler对象的声明位置

private readonly SocketsHttpHandler _underlyingHandler;

这里说明一下,HttpClient的实质工作类是HttpClientHandler,而HttpClientHandler真正发起请求是依靠的SocketsHttpHandler这个类,也就是说SocketsHttpHandler是最原始发起请求的类。HttpClientHandler本质还是通过SocketsHttpHandler发起的Http请求,接下来我们就查看SocketsHttpHandler类是如何处理AutomaticDecompression这个属性的

public DecompressionMethods AutomaticDecompression
{
    get => _settings._automaticDecompression;
    set
    {
        CheckDisposedOrStarted();
        _settings._automaticDecompression = value;
    }
}

这里的_settings不再是具体的功能类,而是用于初始化或者保存SocketsHttpHandler的部分属性值的配置类

private readonly HttpConnectionSettings _settings = new HttpConnectionSettings();

这里我们不在分析SocketsHttpHandler出处理响应压缩之外的其他代码,所以具体就不再看这些了,直接查找_settings._automaticDecompression属性引用的地方,最终找到了这段代码

if (settings._automaticDecompression != DecompressionMethods.None)
{
    handler = new DecompressionHandler(settings._automaticDecompression, handler);
}

这里就比较清晰了,真正处理请求响应压缩相关的都是在DecompressionHandler中。正如我们之前所说的,HttpClient真正的工作方式就是一些实现自HttpMessageHandler的子类在工作,它把不同功能的实现模块都封装成了具体的Handler中。当你需要使用哪个模块的功能,直接使用对应的Handler操作类去发送处理请求即可。这种设计思路在ASP.NET Core中体现的也是淋漓尽致,ASP.NET Core采用的是构建不同终结点去处理和输出请求。通过这些我们可以得知DecompressionHandler才是今天的主题,接下来我们就来查看DecompressionHandler类的源码,我们先来看最核心的SendAsync方法,这个方法是发送请求的执行方法

internal override async ValueTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool async, CancellationToken cancellationToken)
{
    //判断是否是GZIP压缩请求,如果是则添加请求头Accept-Encoding头为gzip
    if (GZipEnabled && !request.Headers.AcceptEncoding.Contains(s_gzipHeaderValue))
    {
        request.Headers.AcceptEncoding.Add(s_gzipHeaderValue);
    }
    //判断是否是Deflate压缩请求,如果是则添加请求头Accept-Encoding头为deflate
    if (DeflateEnabled && !request.Headers.AcceptEncoding.Contains(s_deflateHeaderValue))
    {
        request.Headers.AcceptEncoding.Add(s_deflateHeaderValue);
    }
    //判断是否是Brotli压缩请求,如果是则添加请求头Accept-Encoding头为brotli
    if (BrotliEnabled && !request.Headers.AcceptEncoding.Contains(s_brotliHeaderValue))
    {
        request.Headers.AcceptEncoding.Add(s_brotliHeaderValue);
    }
    //发送请求
    HttpResponseMessage response = await _innerHandler.SendAsync(request, async, cancellationToken).ConfigureAwait(false);

    Debug.Assert(response.Content != null);
    //获取返回的Content-Encoding输出头信息
    ICollection<string> contentEncodings = response.Content.Headers.ContentEncoding;
    if (contentEncodings.Count > 0)
    {
        string? last = null;
        //获取最后一个值
        foreach (string encoding in contentEncodings)
        {
            last = encoding;
        }
        //根据响应头判断服务端采用的是否为gzip压缩
        if (GZipEnabled && last == Gzip)
        {
            //使用gzip解压算法解压返回内容,并从新赋值到response.Content
            response.Content = new GZipDecompressedContent(response.Content);
        }
        //根据响应头判断服务端采用的是否为deflate压缩
        else if (DeflateEnabled && last == Deflate)
        {
            //使用deflate解压算法解压返回内容,并从新赋值到response.Content
            response.Content = new DeflateDecompressedContent(response.Content);
        }
        //根据响应头判断服务端采用的是否为brotli压缩
        else if (BrotliEnabled && last == Brotli)
        {
            //使用brotli解压算法解压返回内容,并从新赋值到response.Content
            response.Content = new BrotliDecompressedContent(response.Content);
        }
    }
    return response;
}

通过上面的逻辑我们可以看到GZipEnabledDeflateEnabledBrotliEnabled三个bool类型的变量,中三个变量决定了采用哪种请求压缩方式,主要实现方式是

internal bool GZipEnabled => (_decompressionMethods & DecompressionMethods.GZip) != 0;
internal bool DeflateEnabled => (_decompressionMethods & DecompressionMethods.Deflate) != 0;
internal bool BrotliEnabled => (_decompressionMethods & DecompressionMethods.Brotli) != 0;

主要就是根据我们配置的DecompressionMethods枚举值判断想获取哪种方式的压缩结果,解压的实现逻辑都封装在GZipDecompressedContentDeflateDecompressedContentBrotliDecompressedContent中,我们看一下他们的具体的代码

private sealed class GZipDecompressedContent : DecompressedContent
{
        public GZipDecompressedContent(HttpContent originalContent)
            : base(originalContent)
        { }
        //使用GZipStream类对返回的流进行解压
        protected override Stream GetDecompressedStream(Stream originalStream) =>
            new GZipStream(originalStream, CompressionMode.Decompress);
    }

    private sealed class DeflateDecompressedContent : DecompressedContent
    {
        public DeflateDecompressedContent(HttpContent originalContent)
            : base(originalContent)
        { }
        //使用DeflateStream类对返回的流进行解压
        protected override Stream GetDecompressedStream(Stream originalStream) =>
            new DeflateStream(originalStream, CompressionMode.Decompress);
    }

    private sealed class BrotliDecompressedContent : DecompressedContent
    {
        public BrotliDecompressedContent(HttpContent originalContent) :
            base(originalContent)
        { }
        //使用BrotliStream类对返回的流进行解压
        protected override Stream GetDecompressedStream(Stream originalStream) =>
            new BrotliStream(originalStream, CompressionMode.Decompress);
    }
}

其主要的工作方式就是使用对应压缩算法的解压方法得到原始信息。简单总结一下,HttpClient关于压缩相关的处理机制是,首先根据你配置的DecompressionMethods判断你想使用那种压缩算法。然后匹配到对应的压缩算法后添加Accept-Encoding请求头为你期望的压缩算法。最后根据响应结果获取Content-Encoding输出头信息,判断服务端采用的是哪种压缩算法,并采用对应的解压方法解压获取原始数据。

总结:

通过本次探讨HttpClient关于响应压缩的处理我们可以了解到,HttpClient无论从设计上还是实现方式上都有非常高的灵活性和扩展性,这也是为什么到了.Net Core上官方只推荐使用HttpClient一种Http请求方式。由于使用比较简单,实现方式比较清晰,这里就不过多拗述。主要是是想告诉大家HttpClient默认可以直接处理响应压缩,而不是和之前我们使用HttpWebRequest的时候还需要手动编码的方式去实现。

(0)

相关推荐

  • .NET CORE HttpClient的使用方法

    前言 自从HttpClient诞生依赖,它的使用方式一直备受争议,framework版本时代产生过相当多经典的错误使用案例,包括Tcp链接耗尽.DNS更改无感知等问题.有兴趣的同学自行查找研究.在.NETCORE版本中,提供了IHttpClientFactory用来创建HttpClient以解决之前的种种问题.那么我们一起看一下它的用法. 使用方式 基本用法. 直接注入IHttpClientFactory 命名客户端.注入IHttpClientFactory并带有名称,适用于需要特定的客户端配置

  • .NET Core使用HttpClient进行表单提交时遇到的问题

    问题# 在开发微信支付的小微商户进件接口时,需要通过表单来上传身份证图片等数据.在微信支付接口文档也说明了,需要使用 multipart/form-data 的方式发送请求..NET 提供了 MultipartFormDataContent 类型,帮助我们构建表单请求,故有以下代码: var form = new MultipartFormDataContent() { {new StringContent("Value"),"Name}, {new ByteArrayCon

  • ASP.NET Core中的响应压缩的实现

    介绍# 响应压缩技术是目前Web开发领域中比较常用的技术,在带宽资源受限的情况下,使用压缩技术是提升带宽负载的首选方案.我们熟悉的Web服务器,比如IIS.Tomcat.Nginx.Apache等都可以使用压缩技术,常用的压缩类型包括Brotli.Gzip.Deflate,它们对CSS.JavaScript.HTML.XML 和 JSON等类型的效果还是比较明显的,但是也存在一定的限制对于图片效果可能没那么好,因为图片本身就是压缩格式.其次,对于小于大约150-1000 字节的文件(具体取决于文

  • Asp.Net Core2.1前后使用HttpClient的两种方式

    前言 在.Net Core应用开发中,调用第三方接口也是常有的事情,HttpClient使用人数.使用频率算是最高的一种了,在.Net Core中,HttpClient的使用方式随着版本的升级也发生了一些变化,本次就讲解一下Asp.Net Core2.1前后使用的两种方式. 一.原先HttpClient使用方式 一般来讲,喜欢要用的时候才会选择去获取资源,因此,当在有需求时才会用HttpClient去调用资源,便会使用如下这种方式或其它方式获取资源. //do something... usin

  • ASP.NET Core针对一个使用HttpClient对象的类编写单元测试详解

    介绍 几年前,微软引入了HttpClient类来替代HttpWebRequest来发送Web请求.这个新的类更易于使用,更加简洁,更具有异步性,且易于扩展. HttpClient类有一个可以接受HttpMessageHandler类对象的构造函数.HttpMessageHandler类对象可以接受一个请求(HttpRequestMessage), 并返回响应(HttpResponseMessage).它的功能完全取决于它的实现.默认情况下HttpClient使用的是HttpClientHandl

  • .Net Core下HTTP请求IHttpClientFactory示例详解

    使用方式 IHttpClientFactory有四种模式: 基本用法 命名客户端 类型化客户端 生成的客户端 基本用法 在 Startup.ConfigureServices 方法中,通过在 IServiceCollection 上调用 AddHttpClient 扩展方法可以注册 IHttpClientFactory services.AddHttpClient(); 注册之后可以像依赖注入DI似得在类中通过构造函数注入形式使用,伪代码: class A { private readonly

  • asp.net core为IHttpClientFactory添加动态命名配置

    目录 官方有什么推荐么? IHttpClientFactory.CreateClient是如何将HttpClient创建出来的? 扩展点一的实现 扩展点二的实现 使用 总结一下 比如如何使用IHttpClientFactory动态添加cer证书 有三种方法推荐方法 方法一: 推荐的做法是这样子 services.AddHttpClient("a业务").ConfigurePrimaryHttpMessageHandler(...a业务证书) services.AddHttpClient

  • .NET Core中使用HttpClient的正确姿势

    前言 为了更方便在服务端调用 HTTP 请求,微软在 .NET Framework 4.x 的时候引入了 HttpClient.但 HttpClient 有很多严重问题,一直饱受诟病,比如 InfoQ 的这篇文章 t.cn/Evzy80y,吐槽了 HttpClient 不能立即关闭连接.性能消耗严重等的问题. Http协议的重要性相信不用我多说了,HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性,它不仅是客户端发送Http请求变得容易,而且也方便了开发人员

  • .NET Core 2.1中HttpClientFactory的最佳实践记录

    前言 ASP.NET Core 2.1中出现一个新的HttpClientFactory功能, 它有助于解决开发人员在使用HttpClient实例从其应用程序发出外部Web请求时可能遇到的一些常见问题. 介绍 在.NETCore平台的2.1新增了HttpClientFactory,虽然HttpClient这个类实现了disposable,但使用它的时候用声明using包装块的方式通常不是最好的选择.处理HttpClient,底层socket套接字不会立即释放.该HttpClient类是专为多个请求

  • 如何在ASP.NET Core中使用HttpClientFactory

    ASP.Net Core 是一个开源的,跨平台的,轻量级模块化框架,可用它来构建高性能的Web程序,这篇文章我们将会讨论如何在 ASP.Net Core 中使用 HttpClientFactory. 为什么要使用 HttpClientFactory 可以用 HttpClientFactory 来集中化管理 HttpClient,工厂提供了对 HttpClient 的创建,配置和调度,值得一提的是:HttpClient 一直都是 Http 请求业务方面的一等公民. HttpClient 虽好,但它

  • 在ASP.NET Core中用HttpClient发送POST, PUT和DELETE请求

    在上一篇文章中,我们已经学习了如何在ASP.NET Core中使用HttpClient从Web API获取数据.此外,我们还学习了如何使用GetAsync方法和HttpRequestMessage类发送GET请求.在本文中,我们将学习如何在ASP.NET Core中使用HttpClient发送POST.PUT和DELETE请求,并使用PostAsync.PutAsync.DeleteAsync和HttpRequestMessage类创建请求. 在ASP.NET Core中使用HttpClient

  • ASP.NET Core 文件响应压缩的常见使用误区

    误区1:未使用 Brotil 压缩 几乎不需要任何额外的代价,Brotil 压缩算法可以帮助你的网站提升约 20% 静态资源加载性能. 同时启用 Gzip / Brotil 压缩 Gzip 有更好的 user-agent 兼容性,而 Brotli 有更好的性能. 所以我们通常需要在 ASP.NET Core 网站中同时启用这两种压缩. 如何区分 Gzip 压缩和 Brotli 压缩 网站启用 Brotli 压缩时,服务器请求返回头 Content-Encoding 中会包含 br 字样,否则是

  • .NET Core中HttpClient的正确打开方式

    前言 在 Asp.Net Core 1.0 时代,由于设计上的问题, HttpClient 给开发者带来了无尽的困扰,用 Asp.Net Core 开发团队的话来说就是:我们注意到,HttpClient 被很多开发人员不正确的使用.得益于 .Net Core 不断的版本快速升级: 问题来源 长期以来,.NET开发者都通过下面的方式发送http请求: using (var httpClient = new HttpClient()) { var response = await httpClien

随机推荐