Preface:
In previous articlesImplementation of response compression in CoreIt is mentioned that the main job of the server is based onContent-Encoding
The header information is determined in which way to compress and return. Someone in the group asked before, is it necessary to compress the requests on the server with such high network bandwidth now? Indeed, nowadays, distributed and load balancing technologies are so mature, many scenarios that require processing high-concurrency big data can be performed by adding server nodes. However, when resources are limited, or when there is no need to add new server nodes for a certain point, we still need to use some conventional processing methods of the program itself to handle it. The author personally believes that the use scenario of response compression is like this. When the bandwidth pressure is relatively tight and the CPU resources are sufficient, the overall effect of using response compression is still relatively obvious.
There is compression and decompression, and the decompression is to request the client to handle it. For example, browsers are our most commonly used Http client. Many browsers default to when we make requests (such as when we browse web pages).Request Head
Added inContent-Encoding
, and then process the relevant decompression according to the response information. All of this is because the browser already has built-in mechanisms about requesting compression and decompression. There are many similar ones, such as the commonly used proxy packet capture toolFilder
This mechanism is also built-in. It just needs to be processed manually, but the implementation method is the same. Sometimes we also need to use this mechanism in the process of writing programs ourselves, in the traditional.Net HttpWebRequest
There is no such mechanism in the class library, and later it was added to the version.HttpClient
, There is a built-in mechanism to handle this operation.
1. How to use
First, let's take a look at it directlyHttpClient
How to deal with response compression
//Customize HttpClientHandler instanceHttpClientHandler httpClientHandler = new HttpClientHandler { AutomaticDecompression = }; //Use the constructor that passes a custom HttpClientHandler instanceusing (HttpClient client = new HttpClient(httpClientHandler)) { var response = await ($"http://MyDemo/Home/GetPerson?userId={userId}"); }
This operation is very simple, we are not operatingHttpClient
butHttpClientHandler
The properties in our previous articles[.NET Core HttpClient
It has been discussed in Source Code Exploration],HttpClient
The essence is actuallyHttpMessageHandler
,andHttpClient
What is really used isHttpMessageHandler
The most important subclassHttpClientHandler
, all request operations are passedHttpMessageHandler
Progress. We can seeAutomaticDecompression
What you accept isDecompressionMethods
Enumeration, since it is an enum, it means that it contains more than one value. Let's check it out next.DecompressionMethods
Source code in
[Flags] public enum DecompressionMethods { // Use all compression decompression algorithms. All = -1, // No decompression None = 0x0, // Use gzip decompression algorithm GZip = 0x1, // Use deflate decompression algorithm Deflate = 0x2, // Use Brotli decompression algorithm Brotli = 0x4 }
This enumeration is based on common output decompression algorithms by default. Let's take a look at it.HttpClientFactory
How to deal with response compression. In previous articles [.NETCore HttpClientFactory+Consul
We have discussed in Implementing Service Discovery]HttpClientFactory
The roughly working method is defaultPrimaryHandler
The HttpClientHandler instance is passed, and it is registered with usHttpClientFactory
It can be passed whenConfigurePrimaryHttpMessageHandler
CustomizePrimaryHandler
The default value, next we implement the specific code
("mydemo", c => { = new Uri("http://MyDemo/"); }).ConfigurePrimaryHttpMessageHandler(provider=> new HttpClientHandler { AutomaticDecompression = });
Actually registeringHttpClientFactory
You can also use customHttpClient
The specific usage method is like this
("mydemo", c => { = new Uri("http://MyDemo/"); }).ConfigureHttpClient(provider => new HttpClient(new HttpClientHandler { AutomaticDecompression = }));
HttpClient
It has indeed helped us do a lot of things, and we only need to simply configure it to enable the processing of response compression. This has aroused us even moreHttpClient
Next, we will check how it initiates a compressible request and decompresses the response result through the source code.
2. Source code exploration
Through the above usage, we know that no matter which form is used, it will ultimately be targeted.HttpClientHandler
Do configuration operations, let's check it outHttpClientHandler
In classAutomaticDecompression
The code of the attribute
public DecompressionMethods AutomaticDecompression { get => _underlyingHandler.AutomaticDecompression; set => _underlyingHandler.AutomaticDecompression = value; }
Its own value operation comes from_underlyingHandler
This object, that is, both read and set are in operation_underlyingHandler.AutomaticDecompression
, we found_underlyingHandler
The declared location of the object
private readonly SocketsHttpHandler _underlyingHandler;
Let me explain it here,HttpClient
The substantive work category isHttpClientHandler
,andHttpClientHandler
The real request is based onSocketsHttpHandler
This class, that isSocketsHttpHandler
is the original class that initiates the request.HttpClientHandler
In essence, it is throughSocketsHttpHandler
The Http request initiated, let's check it nextSocketsHttpHandler
How are classes handledAutomaticDecompression
This property
public DecompressionMethods AutomaticDecompression { get => _settings._automaticDecompression; set { CheckDisposedOrStarted(); _settings._automaticDecompression = value; } }
Here_settings
It is no longer a specific functional class, but is used for initialization or storageSocketsHttpHandler
Configuration class for some attribute values
private readonly HttpConnectionSettings _settings = new HttpConnectionSettings();
We are not analyzing hereSocketsHttpHandler
Out other codes other than processing response compression, so I won't look at these in detail, just look up_settings._automaticDecompression
The attribute reference is where the code is finally found
if (settings._automaticDecompression != ) { handler = new DecompressionHandler(settings._automaticDecompression, handler); }
It's clearer here. The real process of request response compression is all related toDecompressionHandler
middle. As we said before,HttpClient
The real way to work is to implement itHttpMessageHandler
The subclass of the work is working, and it encapsulates the implementation modules of different functions into specificHandler
middle. When you need to use the function of which module, use the corresponding module directlyHandler
Just send a processing request by the operation class. This design idea is Core
It is also vividly reflected in it. Core
It uses different endpoints to process and output requests. Through these we can knowDecompressionHandler
This is the theme today, let's check it out nextDecompressionHandler
Let's first look at the most core source codeSendAsync
Method, this method is the execution method of sending a request
internal override async ValueTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool async, CancellationToken cancellationToken) { //Judge whether it is a GZIP compression request. If so, add the request header Accept-Encoding header to gzip if (GZipEnabled && !(s_gzipHeaderValue)) { (s_gzipHeaderValue); } //Judge whether it is a Deflate compression request. If so, add the request header Accept-Encoding header to deflate if (DeflateEnabled && !(s_deflateHeaderValue)) { (s_deflateHeaderValue); } //Judge whether it is a Brotli compression request. If so, add the request header Accept-Encoding header to brotli if (BrotliEnabled && !(s_brotliHeaderValue)) { (s_brotliHeaderValue); } //Send a request HttpResponseMessage response = await _innerHandler.SendAsync(request, async, cancellationToken).ConfigureAwait(false); ( != null); //Get the returned Content-Encoding output header information ICollection<string> contentEncodings = ; if ( > 0) { string? last = null; //Get the last value foreach (string encoding in contentEncodings) { last = encoding; } //Judge whether the server uses gzip compression based on the response header if (GZipEnabled && last == Gzip) { //Use gzip decompression algorithm to decompress the return content and assign it from the new value to = new GZipDecompressedContent(); } //Judge whether the server uses deflate compression based on the response header else if (DeflateEnabled && last == Deflate) { //Use deflate decompression algorithm to decompress the return content and assign it from the new value to = new DeflateDecompressedContent(); } //Judge whether the server uses brotli compression based on the response header else if (BrotliEnabled && last == Brotli) { //Use brotli decompression algorithm to decompress the return content and assign it from the new value to = new BrotliDecompressedContent(); } } return response; }
Through the above logic we can seeGZipEnabled
、DeflateEnabled
、BrotliEnabled
The three variables of bool type determine which request compression method is adopted. The main implementation method is
internal bool GZipEnabled => (_decompressionMethods & ) != 0; internal bool DeflateEnabled => (_decompressionMethods & ) != 0; internal bool BrotliEnabled => (_decompressionMethods & ) != 0;
It is mainly based on our configurationDecompressionMethods
Enumeration value determines which method of compression result to obtain, and the implementation logic of decompression is encapsulated inGZipDecompressedContent
、DeflateDecompressedContent
、BrotliDecompressedContent
middle,Let's take a look at their specific code
private sealed class GZipDecompressedContent : DecompressedContent { public GZipDecompressedContent(HttpContent originalContent) : base(originalContent) { } //Use the GZipStream class to decompress the returned stream protected override Stream GetDecompressedStream(Stream originalStream) => new GZipStream(originalStream, ); } private sealed class DeflateDecompressedContent : DecompressedContent { public DeflateDecompressedContent(HttpContent originalContent) : base(originalContent) { } //Use the DeflateStream class to decompress the returned stream protected override Stream GetDecompressedStream(Stream originalStream) => new DeflateStream(originalStream, ); } private sealed class BrotliDecompressedContent : DecompressedContent { public BrotliDecompressedContent(HttpContent originalContent) : base(originalContent) { } //Use BrotliStream class to decompress the returned stream protected override Stream GetDecompressedStream(Stream originalStream) => new BrotliStream(originalStream, ); } }
The main working method is to use the decompression method of the corresponding compression algorithm to obtain the original information. To summarize briefly,HttpClient
The compression-related processing mechanism is, first according to your configurationDecompressionMethods
Determine which compression algorithm you want to use. Then match the corresponding compression algorithm and add Accept-Encoding
The request header is the compression algorithm you expect. Finally, obtain based on the response resultContent-Encoding
Output header information, determine which compression algorithm is used by the server, and decompress the original data using the corresponding decompression method.
Summarize:
Through this discussionHttpClient
We can learn about the processing of response compression,HttpClient
There are very high flexibility and scalability in both design and implementation methods, which is why.Net Core
Officially recommended onlyUse HttpClient
A way to request Http. Since it is simple to use and clearer to implement it, I will not explain it too much here. Mainly I want to tell everyoneHttpClient
By default, the response compression can be processed directly, rather than as we used beforeHttpWebRequest
It also needs to be implemented manually.