Сжатие ответа

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7

Последнее обновление: 19.12.2019

Компрессия или сжатие ответа представляет один из инструментов, которые позволяют уменьшить объем отправляемых клиенту данных без потери самих данных.

Для компрессии ответа в рамках ASP.NET Core мы можем использовать либо внутренние средства конкретного веб-сервера: модуль динамического сжатия (Dynamic Compression module) в IIS, модуль mod_deflate в Apache или инструменты компрессии в Nginx. Однако эти инструменты не всегда бывают доступны, особенно когда приложение напрямую хостится в рамках таких веб-серверов как HTTP.sys или Kestrel. И в этом случае мы можем воспользоваться специальным компонентом middleware - Microsoft.AspNetCore.ResponseCompression. Рассмотрим, как использовать этот компонент.

Для этого создадим возбмем проект ASP.NET Core по типу Empty. Этот проект уже содержит нужный нам Nuget-пакет Microsoft.AspNetCore.ResponseCompression.

Теперь используем middleware в классе Startup:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace CompressionApp
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            // добавляем сервис компрессии
            services.AddResponseCompression(options=>options.EnableForHttps = true);
        }
        
        public void Configure(IApplicationBuilder app)
        {
            // подключаем компрессию
            app.UseResponseCompression();

            app.Run(async context =>
            {
                // отправляемый текст
                string loremIpsum = "Lorem Ipsum is simply dummy text ... including versions of Lorem Ipsum.";
                // установка mime-типа отправляемых данных
                context.Response.ContentType = "text/plain";
                // отправка ответа
                await context.Response.WriteAsync(loremIpsum);
            });
        }
    }
}

Вначале нам надо подключить соответствующий сервис в методе ConfigureServices():

services.AddResponseCompression(options=>options.EnableForHttps = true);

Следует учитывать, что для протокола HTTPS по умолчанию отключено сжатие, поэтому в вызове данного метода подключается сжатие и для HTTPS. Однако если HTTPS не используется, то можно сократить вызов метода:

public void ConfigureServices(IServiceCollection services)
{
    services.AddResponseCompression();
}

Затем в методе Configure задействуем middleware сжатия:

app.UseResponseCompression();

И еще важный момент для использования компрессии - при отправке ответа нам обязательно надо установить MIME-тип отправляемых данных:

context.Response.ContentType = "text/plain";

То есть в данном случае отправляем простой текст, поэтому MIME-тип text/plain. На данный момент middleware компрессии поддерживает следующие MIME-типы:

  • text/plain

  • text/css

  • application/javascript

  • text/html

  • application/xml

  • text/xml

  • application/json

  • text/json

При использовании ASP.NET MVC или Razor Pages необязательно указывать MIME-тип ответа.

Запустим проект на выполнение из под сервера Kestrel (не через IIS):

Запуск через Kestrel в ASP.NET Core Компрессия в ASP.NET Core

На уровне протокола HTTP это выражается также в установлении заголовка content-encoding, который сервер вместе с ответом посылает клиенту. По умолчанию этот заголовок имеет значение content-encoding: br, который говорит о том, что используется сжатие в формат Brotli

Теперь уберем из метода Configure применение компрессии:

public void Configure(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        string loremIpsum = "Lorem Ipsum is simply dummy text ... including versions of Lorem Ipsum.";
        await context.Response.WriteAsync(loremIpsum);
    });
}

В этом случае мы получим немного другие значения.

Компрессия в ASP.NET MVC Core

При использовании компрессии надо учитывать обратную сторону компрессии - уменьшение производительности: затраты на компрессию могут превосходить выгоду от уменьшения объема данных. Поэтому, для небольших объемов (меньше 1 кб) не очень рационально применение компрессии.

Провайдеры компрессии

Механизм компрессии на уровне протокола HTTP концентрруется вокруг двух заголовков. Вначале клиент в запросе посылает заголовок Accept-Encoding, в котором уведомляет сервер, какие форматы сжатия он поддерживает. Сервер, получив запрос, использует этот заголовок для выбора формата сжатия и затем посылает вместе с ответом клиенту заголовок content-encoding, в котором указывает использованный формат сжатия. Если клиент поддерживает сжатие в формат Brotli, то по умолчанию используется этот формат. Если клиент не поддерживает данный формат, то используется сжатия gzip.

Brotli Compression Provider

В примере выше по умолчанию использовалось сжатие в формат Brotli с помощью класса BrotliCompressionProvider. Он имеет свойство Level, которое устанавливает уровни сжатия и может принимать следующие значения:

  • CompressionLevel.Fastest: сжатие должно происходить как можно быстрее, даже если оно будет неоптимальным в плане уменьшения объема данных.

  • CompressionLevel.NoCompression: сжатие не применяется

  • CompressionLevel.Optimal: данные должны быть максимально сжаты, даже если для этого потребуется больше времени

Для изменения уровня компрессии, можно изменить метод ConfigureServices() следующим образом:

using Microsoft.AspNetCore.ResponseCompression;
using System.IO.Compression;

//............................

public void ConfigureServices(IServiceCollection services)
{
    services.AddResponseCompression(options=>options.EnableForHttps = true);
    
	services.Configure<BrotliCompressionProviderOptions>(options =>
    {
        options.Level = CompressionLevel.Optimal;
    });
}

GzipCompressionProvider

Также мы можем использовать сжатие gzip, а если точнее класс GzipCompressionProvider, который обеспечивает компрессию gzip. Он используется, если клиент не поддерживает сжатие в формат Brotli. Он также имеет свойство Level, которое принимает те же значения, что и BrotliCompressionProvider, и который мы можем настроить подобным образом:

public void ConfigureServices(IServiceCollection services)
{
    services.AddResponseCompression(options=>options.EnableForHttps = true);
    
	services.Configure<GzipCompressionProviderOptions>(options =>
    {
        options.Level = CompressionLevel.Optimal;
    });
}

Создание своих провайдеров

При необходимости мы можем создавать и применять свои провайдеры сжатия. Например, используем сжатие Deflate, и для этого определим следующий класс:

using Microsoft.AspNetCore.ResponseCompression;
using System.IO;
using System.IO.Compression;

namespace CompressionApp
{
    public class DeflateCompressionProvider : ICompressionProvider
    {
        public string EncodingName => "deflate";
        public bool SupportsFlush => true;

        public Stream CreateStream(Stream outputStream)
        {
            return new DeflateStream(outputStream, CompressionLevel.Optimal);
        }
    }
}

Для создания провайдера реализуем интерфейс ICompressionProvider, в котором есть два свойства SupportsFlush и EncodingName и метод CreateStream.

Свойство EncodingName указывает на формат сжатия, который поддерживает клиент. Данный формат содержится в заголовке Accept-Encoding в запросе к серверу, наподобие:

Accept-Encoding: gzip, deflate, sdch, br

То есть данный провайдер будет срабатывать, если клиент прислал в запросе в заголовке Accept-Encoding значение deflate.

Свойство SupportsFlush указывает, поддерживается ли сброс записи в поток.

Метод CreateStream() возвращает сам поток ответа после сжатия или фактически обертка над изначальным потоком ответа, который передается в качестве параметра в метод. Для сжатия применяется встроенный класс DeflateStream().

Применим этот провайдер в методе ConfigureServices() класса Startup:

public void ConfigureServices(IServiceCollection services)
{
    services.AddResponseCompression(options =>
    {
        options.EnableForHttps = true;
        options.Providers.Add(new DeflateCompressionProvider());
    });
}

Для добавления провайдера вызывается метод options.Providers.Add().

Таким образом, если клиент допускает сжатие deflate, то будет использоваться именно провайдер DeflateCompressionProvider:

Defalte compression in ASP.NET Core

Стоит отметить, что если мы добавляем кастомный провайдер компрессии, то в случае если тип сжатия не поддерживается клиентом, то, согласно документации microsoft, следует добавлять и провайдеры по умолчанию BrotliCompressionProvider и GzipCompressionProvider:

public void ConfigureServices(IServiceCollection services)
{
    services.AddResponseCompression(options =>
    {
        options.EnableForHttps = true;
		options.Providers.Add(new DeflateCompressionProvider());
		options.Providers.Add<BrotliCompressionProvider>();
		options.Providers.Add<GzipCompressionProvider>();
    });
}

В итоге при получении запроса провайдеры будут перебираться в том порядке, в котором они добавлены. И первый попавшийся провайдер, формат сжатия которого поддерживается клиентом, и будет выбран для осуществления компрессии.

Добавление MIME-типа

Выше был перечислен весь набор MIME-типов по умолчанию, которые используются в Microsoft.AspNetCore.ResponseCompression, но при необходимости мы можем добавить новый MIME-тип:

services.AddResponseCompression(options =>
{
	options.EnableForHttps = true;
    options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[]
    {
        "image/svg+xml",
        "application/atom+xml"
    }); 
    options.Providers.Add<GzipCompressionProvider>();
});

Динамическая компрессия в IIS

Если мы используем в качестве прокси-сервера IIS, то мы можем воспользоваться модулем динамической компрессии, более того по умолчанию он включен. Если же модуль необходимо отключить, то в проекте в файле конфигурации web.config нужно добавить элемент <urlCompression> и указать у него атрибут doStaticCompression="false"

Если мы работаем с Visual Studio 2017/2019, то в ней по умолчанию файл web.config отсутствует. Поэтому добавим его:

Модуль urlCompression в ASP.NET Core

Изменим файл web.config, использовав элемент urlCompression:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
    </handlers>
    <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
    
	<urlCompression doDynamicCompression="true"/> 
	
</system.webServer>
</configuration>

Но даже если мы используем IIS, это не значит, что мы не можем вместе с этим использовать и компонент Microsoft.AspNetCore.ResponseCompression, правда, это не сильно увеличит сжатие. К примеру, были протестированы различные ситуации в использовании компрессии для одного и того же динамически формируемого документа html:

  • IIS без модуля динамической компрессии - 7,7 кБ

  • IIS с включенным модулем динамической компрессии - 2,5 кБ

  • IIS с включенным модулем динамической компрессии и Microsoft.AspNetCore.ResponseCompression - 2,3 кБ

  • Kestrel без Microsoft.AspNetCore.ResponseCompression - 7,5 кБ

  • Kestrel с Microsoft.AspNetCore.ResponseCompression - 2,1 кБ

Таким образом, сжатие, что через Microsoft.AspNetCore.ResponseCompression, что через модуль динамической компрессии IIS, дает неплохое уменьшение объема отправляемых данных, в то же время различные ситуации сжатия несильно будут отличаться.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850