前言

    在上篇文章[ASP.NET Core中的响应压缩]中我们谈到了在ASP.NET Core服务端处理关于响应压缩的请求,服务端的主要工作就是根据Content-Encoding头信息判断采用哪种方式压缩并返回。之前在群里有人问道过,现在的网络带宽这么高了还有必要在服务端针对请求进行压缩吗?确实,如今分布式和负载均衡技术这么成熟,很多需要处理高并发大数据的场景都可以通过增加服务器节点来进行。但是,在资源受限的情况下,或者是还没必要为了某一个点去增加新的服务器节点的时候,我们还是要采用一些程序本身的常规处理手段来进行处理。笔者个人认为响应压缩的使用场景是这样的,在带宽压力比较紧张的情况,且CPU资源比较充足的情况下,使用响应压缩整体效果还是比较明显的。

    有压缩就有解压,而解压的工作就是在请求客户端处理的。比如浏览器,这是我们最常用的Http客户端,许多浏览器都是默认在我们发出请求的时候(比如我们浏览网页的时候)在Request Head中添加Content-Encoding,然后根据响应信息处理相关解压。这些都源于浏览器已经内置了关于请求压缩和解压的机制。类似的还有许多,比如常用的代理抓包工具Filder也是内置这种机制的。只不过需要手动去处理,但实现方式都是一样的。有时候我们在自己写程序的过程中也需要使用这种机制,在传统的.Net HttpWebRequest类库中,并没有这种机制,后来版本中加入了HttpClient,有自带的机制可以处理这种操作,.Net Core作为后起之秀直接将HttpClient扶正,并且在此基础上改良了HttpClientFactory,接下来我们就来探究一下在.Net Core中使用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;
}

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

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

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

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的时候还需要手动编码的方式去实现。

欢迎扫码关注我的公众号

.Net Core HttpClient处理响应压缩的更多相关文章

  1. ASP.NET Core中的响应压缩

    介绍     响应压缩技术是目前Web开发领域中比较常用的技术,在带宽资源受限的情况下,使用压缩技术是提升带宽负载的首选方案.我们熟悉的Web服务器,比如IIS.Tomcat.Nginx.Apache ...

  2. 解决httpclient请求响应压缩文本乱码问题

    最近在调用京东的获取省份接口老是中文乱码,加了utf-8也没有用.最后在httpclient打的日志中有Content-Encoding:gzip信息,最后在请求header里加上: reqHeade ...

  3. asp.net core 系列之Performance的 Response compression(响应压缩)

    本文,帮助了解响应压缩的一些知识及用法(大部分翻译于官网,英文水平有限,不准确之处,欢迎指正). 什么是响应压缩?响应压缩简单的说就是为了减少网络带宽,而把返回的响应压缩,使之体积缩小,从而加快响应的 ...

  4. asp.net core系列 77 webapi响应压缩

    一.介绍 背景:目前在开发一个爬虫框架,使用了.net core webapi接口作为爬虫调用入口,在调用 webapi时发现爬虫耗时很短(1秒左右),但客户端获取响应时间却在3~4秒.对于这个问题考 ...

  5. [小技巧]ASP.NET Core中如何预压缩静态文件

    原文地址:Pre-compressed static files with ASP.NET Core 作者:Gunnar Peipman 译者:Lamond Lu 译文:https://www.cnb ...

  6. 在ASP.NET Core中使用brotli压缩

    Brotli是一种全新的数据格式,可以提供比Zopfli高20-26%的压缩比.据谷歌研究,Brotli压缩速度同zlib的Deflate实现大致相同,而在Canterbury语料库上的压缩密度比LZ ...

  7. [Linux] Nginx响应压缩gzip

    压缩和解压缩 .本节介绍如何配置响应的压缩或解压缩以及发送压缩文件. gzip on; .NGINX仅使用MIME类型text / html压缩响应 gzip_types text/plain app ...

  8. .NET Core HttpClient调用腾讯云对象存储Web API的"ERROR_CGI_PARAM_NO_SUCH_OP"问题

    开门见山地说一下问题的原因:调用 web api 时请求头中多了双引号,请求体中少了双引号. 腾讯云提供的对象存储(COS)C# SDK 是基于 .NET Framework 用 WebRequest ...

  9. ASP.NET Core 资源打包与压缩

    ASP.NET Core 资源打包与压缩 在ASP.NET 中可以使用打包与压缩来提高Web应用程序页面加载的性能. 打包是将多个文件(CSS,JS等资源文件)合并或打包到单个文件.文件合并可减少We ...

随机推荐

  1. python 并发专题(二):python线程以及线程池相关以及实现

    一 多线程实现 线程模块 - 多线程主要的内容:直接进行多线程操作,线程同步,带队列的多线程: Python3 通过两个标准库 _thread 和 threading 提供对线程的支持. _threa ...

  2. redis(十三):Redis 集合(Set) python

    # -*- coding: utf-8 -*- import redis r = redis.Redis(host="126.56.74.190",port=639,passwor ...

  3. v-bind v-on 缩写

    Vue.js 为两个最为常用的指令提供了特别的缩写:

  4. react 实战:写一个年份选择器

    上代码. 组件的Js文件. import React, { Component } from "react"; import Style from './myYearSelect. ...

  5. JavaScript中的var,const,let区别与用法(浅谈)

    let 和 const是(ES6) 新增加了两个重要的 JavaScript 关键字. 1.var全局变量 //全局变量在 JavaScript 程序的任何地方都可以访问 //定义的变量可以修改,如果 ...

  6. Docker 概念-1

    阅读本文大概需要15分钟,通过阅读本文你将知道一下概念: 容器 什么是Docker? Docker思想.特点 Docker容器主要解决什么问题 容器 VS 虚拟机 Docker基本概念: 镜像(Ima ...

  7. 第 13 篇:DRF 框架之 API 版本管理

    作者:HelloGitHub-追梦人物 API 不可能一成不变,无论是新增或者删除已有 API,都会对调用它的客户端产生影响.如果对 API 的增删没有管理,随着 API 的增增减减,调用它的客户端就 ...

  8. 谁能告诉我如何通过Jenkins完成分布式环境搭建并执行自动化脚本

    ​今天我们接着昨天的内容,看一看如何完成Jenkins分布式环境的搭建和使用,因为我之前也是自己一个人摸索的,如果有不对的地方,请各位看官私信指出. 新增分布式部署节点 在系统管理/节点管理中点击新建 ...

  9. JVM系列之:String.intern和stringTable

    目录 简介 intern简介 intern和字符串字面量常量 分析intern返回的String对象 分析实际的问题 G1中的去重功能 总结 简介 StringTable是什么?它和String.in ...

  10. python socket函数详解

    关于socket函数,每个的意义和基本功能都知道,但每次使用都会去百度,参数到底是什么,返回值代表什么意义,就是说用的少,也记得不够精确.每次都查半天,经常烦恼于此.索性都弄得清楚.通透,并记录下来, ...