Компрессия или сжатие ответа представляет один из инструментов, которые позволяют уменьшить объем отправляемых клиенту данных без потери самих данных.
Для компрессии ответа в рамках ASP.NET Core мы можем использовать либо внутренние средства конкретного веб-сервера: модуль динамического сжатия (Dynamic Compression module) в IIS, модуль mod_deflate в Apache или инструменты компрессии в Nginx. Однако эти инструменты не всегда бывают доступны, особенно когда приложение напрямую хостится в рамках таких веб-серверов как HTTP.sys или Kestrel. И в этом случае мы можем воспользоваться специальным компонентом middleware - Microsoft.AspNetCore.ResponseCompression. Рассмотрим, как использовать этот компонент.
Для этого возьмем проект ASP.NET Core и в файле Program.cs определим простейшее приложение, которое отправляет длинную строку и применяет кэширование:
var builder = WebApplication.CreateBuilder(args); // добавляем сервисы сжатия builder.Services.AddResponseCompression(options => options.EnableForHttps = true); var app = builder.Build(); // подключаем сжатие app.UseResponseCompression(); app.MapGet("/", () => "Lorem Ipsum is simply dummy text of the printing and typesetting industry...exact original form, accompanied by English versions from the 1914 translation by H. Rackham."); app.Run();
Вначале нам надо подключить соответствующий сервис:
builder.Services.AddResponseCompression(options=>options.EnableForHttps = true);
Следует учитывать, что для протокола HTTPS по умолчанию отключено сжатие, поэтому в вызове данного метода подключается сжатие и для HTTPS. Однако если HTTPS не используется, то можно сократить вызов метода:
builder.Services.AddResponseCompression();
Затем в методе Configure задействуем middleware сжатия:
app.UseResponseCompression();
Запустим проект на выполнение:
На уровне протокола HTTP это выражается также в установлении заголовка content-encoding, который сервер вместе с ответом посылает
клиенту. По умолчанию этот заголовок имеет значение content-encoding: br
, который говорит о том, что используется сжатие
в формат Brotli
Теперь уберем из метода Configure применение компрессии - удалим или закомментируем следующую строку:
app.UseResponseCompression();
В этом случае мы получим немного другие значения.
При использовании компрессии надо учитывать обратную сторону компрессии - уменьшение производительности: затраты на компрессию могут превосходить выгоду от уменьшения объема данных. Поэтому, для небольших объемов (меньше 1 кб) не очень рационально применение компрессии.
При использования компрессии при отправке ответа нам обязательно надо установить MIME-тип отправляемых данных:
Так, в примере выше при отправке ответа в конечной точке
app.MapGet("/", () => "Lorem Ipsum ...
Заголовок типа ответа устанавливался автоматически. При использовании ASP.NET MVC или Razor Pages также необязательно указывать MIME-тип ответа.
Однако не все middleware и компоненты в конвейере ASP.NET Core автоматически устанавливают заголовки. Например, при обработке запроса с помощью метода Run
заголовки не устанавливаются,
и их надо устанавливать вручную:
var builder = WebApplication.CreateBuilder(args); // добавляем сервисы сжатия builder.Services.AddResponseCompression(options => options.EnableForHttps = true); var app = builder.Build(); // подключаем сжатие app.UseResponseCompression(); app.Run(async (context) => { // устанавливаем MIME-тип ответа context.Response.ContentType = "text/plain"; // отправляем простой текст await context.Response.WriteAsync("Lorem Ipsum is simply dummy text of the printing and typesetting industry...exact original form, accompanied by English versions from the 1914 translation by H. Rackham."); }); app.Run();
То есть в данном случае отправляем простой текст, поэтому MIME-тип text/plain
. На данный момент middleware компрессии поддерживает
следующие MIME-типы:
text/plain
text/css
application/javascript
text/html
application/xml
text/xml
application/json
text/json
Метод AddResponseCompression()
принимает делегат, который принимает объект ResponseCompressionOptions. Через свойства этого объекта можно установить параметры
кэширования:
EnableForHttps устанавливает доступность сжатия для запросов по https
Providers представляет коллекцию провайдеров компрессии, которые применяются для сжатия ответа. Каждый провайлер представлен типом ICompressionProvider. Для добавления провайдера
в эту коллекцию используется метод Add()
, а для удаления - метод Remove()
.
MimeTypes определяет Mime-типы ответа, к которым применяется сжатие
ExcludedMimeTypes устанавливает Mime-типы ответа, к которым НЕ применяется сжатие
Например, исключим из сжатия простой текст:
builder.Services.AddResponseCompression(options => { options.EnableForHttps = true; // исключаем из сжатия простой текст options.ExcludedMimeTypes = new[] { "text/plain" }; });
Механизм компрессии на уровне протокола HTTP сосредоточен вокруг двух заголовков. Вначале клиент в запросе посылает заголовок Accept-Encoding, в котором уведомляет сервер, какие форматы сжатия он поддерживает. Сервер, получив запрос, использует этот заголовок для выбора формата сжатия и затем посылает вместе с ответом клиенту заголовок Сontent-Encoding, в котором указывает использованный формат сжатия. Если клиент поддерживает сжатие в формат Brotli, то по умолчанию используется этот формат. Если клиент не поддерживает данный формат, то используется сжатия gzip.
В примере выше по умолчанию использовалось сжатие в формат Brotli с помощью класса BrotliCompressionProvider. Он имеет свойство Level, которое устанавливает уровни сжатия и может принимать следующие значения:
CompressionLevel.Fastest: сжатие должно происходить как можно быстрее, даже если оно будет неоптимальным в плане уменьшения объема данных.
CompressionLevel.NoCompression: сжатие не применяется
CompressionLevel.Optimal: данные должны быть максимально сжаты, даже если для этого потребуется больше времени
Для изменения уровня компрессии можно применить метод Configure()
объекта ServiceCollection:
using Microsoft.AspNetCore.ResponseCompression; using System.IO.Compression; var builder = WebApplication.CreateBuilder(args); // добавляем сервисы сжатия builder.Services.AddResponseCompression(options => options.EnableForHttps = true); // устанавливаем уровень сжатия builder.Services.Configure(options => options.Level = CompressionLevel.Optimal); var app = builder.Build(); // подключаем сжатие app.UseResponseCompression(); app.MapGet("/", () => "Lorem Ipsum is simply dummy text of the printing and typesetting industry...exact original form, accompanied by English versions from the 1914 translation by H. Rackham."); app.Run();
Также мы можем использовать сжатие gzip, а если точнее класс GzipCompressionProvider, который обеспечивает компрессию gzip. Он используется, если клиент не поддерживает сжатие в формат Brotli. Для его использования его надо добавить в коллекцию провайдеров сжатия:
using Microsoft.AspNetCore.ResponseCompression; using System.IO.Compression; var builder = WebApplication.CreateBuilder(args); // добавляем сервисы сжатия builder.Services.AddResponseCompression(options => { options.EnableForHttps = true; // добавляем провайдер gzip-сжатия options.Providers.Add(new GzipCompressionProvider(new GzipCompressionProviderOptions())); }); var app = builder.Build(); // подключаем сжатие app.UseResponseCompression(); app.MapGet("/", () => "Lorem Ipsum is simply dummy text of the printing and typesetting industry...exact original form, accompanied by English versions from the 1914 translation by H. Rackham."); app.Run();
В качестве параметра конструктор GzipCompressionProvider принимает объект GzipCompressionProviderOptions
, который устанавливает настройки gzip-сжатия.
Этот объект также имеет свойство Level, которое принимает те же значения, что и BrotliCompressionProvider:
builder.Services.Configure<GzipCompressionProviderOptions>(options => options.Level = CompressionLevel.Optimal);
В качестве альтернативы можно добавлять провайдеры типизацией метода Add()
:
options.Providers.Add<GzipCompressionProvider>();
При необходимости мы можем создавать и применять свои провайдеры сжатия. Например, используем сжатие Deflate, и для этого изменим код программы следующим образом:
using Microsoft.AspNetCore.ResponseCompression; using System.IO.Compression; var builder = WebApplication.CreateBuilder(args); builder.Services.AddResponseCompression(options => { options.EnableForHttps = true; // добавляем провайдер сжатия DeflateCompressionProvider options.Providers.Add(new DeflateCompressionProvider()); }); var app = builder.Build(); // подключаем сжатие app.UseResponseCompression(); app.MapGet("/", () => "Lorem Ipsum is simply dummy text of the printing and typesetting industry...exact original form, accompanied by English versions from the 1914 translation by H. Rackham."); app.Run(); 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().
Стоит отметить, что если мы добавляем кастомный провайдер компрессии, то в случае если тип сжатия не поддерживается клиентом, то лучше также добавлять и провайдеры по умолчанию BrotliCompressionProvider и GzipCompressionProvider:
builder.Services.AddResponseCompression(options => { options.EnableForHttps = true; // добавляем провайдер сжатия DeflateCompressionProvider options.Providers.Add<DeflateCompressionProvider>(); options.Providers.Add<GzipCompressionProvider>(); options.Providers.Add<BrotliCompressionProvider>(); });
В итоге при получении запроса провайдеры будут перебираться в том порядке, в котором они добавлены. И первый попавшийся провайдер, формат сжатия которого поддерживается клиентом, и будет выбран для осуществления компрессии.