.Net Core HttpClient处理响应压缩

前言

    在上篇文章[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头为gzipif (GZipEnabled && !request.Headers.AcceptEncoding.Contains(s_gzipHeaderValue)){request.Headers.AcceptEncoding.Add(s_gzipHeaderValue);}//判断是否是Deflate压缩请求,如果是则添加请求头Accept-Encoding头为deflateif (DeflateEnabled && !request.Headers.AcceptEncoding.Contains(s_deflateHeaderValue)){request.Headers.AcceptEncoding.Add(s_deflateHeaderValue);}//判断是否是Brotli压缩请求,如果是则添加请求头Accept-Encoding头为brotliif (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.Contentresponse.Content = new GZipDecompressedContent(response.Content);}//根据响应头判断服务端采用的是否为deflate压缩else if (DeflateEnabled && last == Deflate){//使用deflate解压算法解压返回内容,并从新赋值到response.Contentresponse.Content = new DeflateDecompressedContent(response.Content);}//根据响应头判断服务端采用的是否为brotli压缩else if (BrotliEnabled && last == Brotli){//使用brotli解压算法解压返回内容,并从新赋值到response.Contentresponse.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的时候还需要手动编码的方式去实现。

????欢迎扫码关注我的公众号????

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/308444.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java容器的遍历之增强for循环

一&#xff1a;为什么要用增强版的 for 循环呢 在普通的数组遍历当中&#xff0c;我们采用普通的for循环即可&#xff0c;但在遍历2.遍历集合、容器&#xff0c;当中我们一般采用增强版的for循环 &#xff0c;简单方便。 二&#xff1a;构造模式 for(数据类型 变量&#xff…

mysql脚本的制作_制作脚本实现mysql自动备份

首先执行vi dbbackup.sh命令&#xff0c;在打开的编辑器输入&#xff1a;#!/bin/bash/usr/local/mysql/bin/mysqldump -uuser -ppasswd databasename > /home/wwwroot/backup/date_$(date%Y%m%d).sql这段命令的意思是&#xff1a;用mysqldump导出名为databasename的数据库到…

在 PostgreSQL 中使用码农很忙 IP 地址数据库

在下载到码农很忙 IP 地址数据库后&#xff0c;我们可以将其存储在 PostgreSQL 数据库中&#xff0c;并在需要查询某个 IP 对应的位置数据时&#xff0c;通过 SQL 语句获取正确的结果。这是一种很便捷的使用方式&#xff0c;并且在增加了恰当的索引后&#xff0c;可以取得不错的…

Java当中Collections的用法

一&#xff1a;上码 package cn.wyj.two;/*** Collections辅助类的使用* * author 王永杰**/ import java.util.*;public class Demo11_Collections辅助类 {public static void main(String[] args) {List<String> list new ArrayList<String>();for( int i 0; …

.NET开发者提高编程技能的5种方法

.NET开发者提高编程技能的5种方法https://insights.dice.com/2017/08/29/5-ways-improve-programming-skills/即使拥有40年的编程经验&#xff0c;我唯一能确定的就是肯定有比我更好的程序员。但是我并没有放弃&#xff0c;我会继续尝试并提高自己的编程技能。我认为有五件事可…

Java当中用 javabean和其他容器存入表格数据 或 利用 容器进行存储表格

一&#xff1a;javabean 和list容器或map容器 package cn.wyj.two;import java.util.*;/*** javabean :必须有一个无参构造函数&#xff1b;变量属性私有化&#xff1b;* 本篇还是打印一张表* author 86155**/ public class Demo13_Javabean和其他容器 {public static void ma…

7-25 朋友圈 (25 分)(详解+并查集的了解和应用)

一&#xff1a;题目 某学校有N个学生&#xff0c;形成M个俱乐部。每个俱乐部里的学生有着一定相似的兴趣爱好&#xff0c;形成一个朋友圈。一个学生可以同时属于若干个不同的俱乐部。根据“我的朋友的朋友也是我的朋友”这个推论可以得出&#xff0c;如果A和B是朋友&#xff0…

使用Azure人脸API对图片进行人脸识别

人脸识别是人工智能机器学习比较成熟的一个领域。人脸识别已经应用到了很多生产场景。比如生物认证&#xff0c;人脸考勤&#xff0c;人流监控等场景。对于很多中小功能由于技术门槛问题很难自己实现人脸识别的算法。Azure人脸API对人脸识别机器学习算法进行封装提供REST API跟…

java while do循环_c语言中,while 和 do while 循环的主要区别是( )

1、循环构造的表达式不同&#xff1a;while循环构造的表达式为&#xff1a;while(表达式)&#xff5b;循环体&#xff5d;。do&#xff0d;while循环构造表达式为&#xff1a;do&#xff5b;循环体&#xff1b;&#xff5d;while(条件表达)&#xff1b;。2、执行末尾循环体的次…

Java当中 IO(File) 操作 之 递归打印子孙级目录和文件名称

一&#xff1a;上码 package com.wyj.one;import java.io.File;/*** 列出下一级&#xff1a;* 1, list(): 列出下一级名称* 2&#xff0c; listFiles()&#xff1a;列出下一级File对象* 3, listRoots():列出盘符* author 86155**/ public class Demo8_列出下一级 {/*** p…

[NewLife.Net]单机400万长连接压力测试

目标对网络库NewLife.Net进行单机百万级长连接测试&#xff0c;并持续收发数据&#xff0c;检测网络库稳定性。【2020年8月1日晚上22点】先上源码&#xff1a;https://github.com/NewLifeX/NewLife.Net结论&#xff0c;8月1日晚达到200万&#xff0c;8月2日下午达到404万。上一…

JAVA开发需求分析套路_毕设做什么好?感觉都是套路了

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼在不考虑柯西序列的情况下&#xff1a;.1.00000000000000000……-0.9999999…….结果为 0.000…&#xff0c;也就是后面的 0 无限循环。这两个数目在这里是无限循环小数&#xff0c;小数点后五位之后还会一直填上 0&#xff0c;始终…

ABP快速开发一个.NET Core电商平台

总听.NETer羡慕Java有SSM框架&#xff0c;其实.NET也有ABP&#xff0c;极度优秀的开源应用程序框架&#xff0c;支持.NET Framework和.NET Core。羡慕Java有SpringCloud&#xff0c;其实.NET也有ABP.vNext&#xff0c;由ABP团队全新打造的.NET Core微服务架构开源框架&#xff…

Java当中 递归打印文件大小(面向不同的对象打印文件大小)

一&#xff1a;给固定的文件路径求文件大小 package com.wyj.one;import java.io.File;/*** 递归求出文件的大小* author 86155**/ public class CopyOfDemo10_递归打印子文件大小 {public static void main(String[] args) {File src new File("C:/Myjava/project12_IO…

java jlist 图标_java – 将图像添加到JList项目

您必须实现ListCellRenderer(或扩展DefaultListCellRenderer)并使用getListCellRendererComponent方法返回带有图标的Jlabel.例&#xff1a;public class IconListRenderer extends DefaultListCellRenderer {public Component getListCellRendererComponent(JList list, Objec…

Java当中编码和解码(以及出现乱编码的原因)

一&#xff1a;编码&#xff1a;字符串---->字节 package com.wyj.two;import java.io.UnsupportedEncodingException;/*** 编码&#xff1a;字符串-->字节* * * author 王永杰**/ public class Demo1_encode编码 {public static void main(String[] args) throws Unsup…

用了Dapper之后通篇还是SqlConnection,真的看不下去了

一&#xff1a;背景1. 讲故事前几天看公司一个新项目的底层使用了dapper&#xff0c;大家都知道dapper是一个非常强大的半自动化orm&#xff0c;帮程序员解决了繁琐的mapping问题&#xff0c;用起来非常爽&#xff0c;但我还是遇到了一件非常不爽的事情,如下代码所示&#xff1…

java swing 控件拖动_java swing中实现拖拽功能示例

java实现拖拽示例Swing中实现拖拽功能&#xff0c;代码很简单&#xff0c;都有注释&#xff0c;自己看&#xff0c;运行效果如下图&#xff1a;package com;import java.awt.*;import java.awt.datatransfer.DataFlavor;import java.awt.dnd.DnDConstants;import java.awt.dnd.…

7-26 Windows消息队列 (25 分)(详解+思路+超时解决)

一&#xff1a;题目 消息队列是Windows系统的基础。对于每个进程&#xff0c;系统维护一个消息队列。如果在进程中有特定事件发生&#xff0c;如点击鼠标、文字改变等&#xff0c;系统将把这个消息加到队列当中。同时&#xff0c;如果队列不是空的&#xff0c;这一进程循环地从…

一文说通C#中的异步编程补遗

前文写了关于C#中的异步编程。后台有无数人在讨论&#xff0c;很多人把异步和多线程混了。文章在这儿&#xff1a;一文说通C#中的异步编程所以&#xff0c;本文从体系的角度&#xff0c;再写一下这个异步编程。一、C#中的异步编程演变1. 异步编程模型这是C#中早期的异步模型&am…