Содержание
Сжатие ответов – это ещё один из наиболее часто применяемых способов повышения производительности веб-приложения. Пропускная способность Сети – ресурс ограниченный и, чем меньший объем данных мы передаем от сервера к клиенту и наоборот, тем наше приложение может стать отзывчивее и быстрее реагировать на запросы пользователей.
При принятии решения о том стоит ли использовать сжатие ответов в своем приложении или нет, в первую очередь стоит провести, хоты бы поверхностный анализ того, какие объемы данных и какие ресурсы обрабатывает ваше приложение. Так, например, нет смысла использовать какие-либо алгоритмы сжатия ответа размером всего в несколько байт – накладные расходы времени на выполнение сжатия такого ответа могут оказаться выше, чем время, за которое сервер может отправить клиенту, а клиент обработать несжатый ответ. То же самое касается и уже изначально сжатых данных. Например, не имеет никакого смысла сжимать перед отправкой файлы png – они уже изначально сжаты, и повторная попытка их сжать может не только не повысить, но и понизить производительность вашего приложения.
Механизм сжатия ответов
Вначале разберемся с тем, как реализуется сжатие ответов в HTTP.
Пользователь отправляет запрос на получение ресурса, включая в запрос заголовок Accept-Encoding
в котором перечисляет поддерживаемые им способы сжатия содержимого ответа.
Сервер, получив ответ, анализирует полученный заголовок Accept-Encoding
и, если хотя бы один из предоставленных клиентом способов сжатия поддерживается, то сервер может вернуть сжатый ответ. Если сервер отправляет сжатый контент, то в ответ обязательно включается заголовок Content-Encoding
, который содержит способ сжатия содержимого. Если же на сервере настроено кэширование ответов, то в ответ так же включается заголовок Vary
со значением Accept-Encoding
, сообщающий нам, что кэшированные данные сжатого и не сжатого ответа различаются. Если сервер не поддерживает никакие из переданных клиентов способы сжатия, то ответ клиенту отправляется в несжатом виде и заголовок Content-Encoding
не включается в ответ.
Директивы заголовка Accept-Encoding
Заголовок Accept-Encoding
может содержать следующие директивы
Директива | Значение |
gzip |
Поддерживается формат сжатия gzip |
br |
Поддерживается формат сжатия Brotli |
compress |
Поддерживается формат сжатия LZW |
deflate |
Поддерживается формат сжатия deflate |
zstd |
Поддерживается формат Zstandart |
identity |
Указывает функцию идентификации (то есть без изменения или сжатия). Это значение всегда считается приемлемым, даже если оно опущено. |
* |
Это значение по умолчанию, если заголовок отсутствует. Директива не предполагает, что поддерживается какой-либо алгоритм, но указывает, что предпочтение не выражено |
Таким образом, клиент может перечислить в заголовке один или несколько поддерживаемых форматов, например:
Accept-Encoding: gzip, deflate, br
и, если на сервере поддерживается хотя бы один формат сжатия и сервер может сжать ответ (сервер не перегружен, не настроены какие-либо ограничения по сжатию ответов и т.д.), то ответ вернется в сжатом виде с использованием одного из поддерживаемых клиентом форматов. Также клиент может определить для сервера приоритетность выбора того или иного формата.
Директива веса
Чтобы указать в каком порядке проверять доступные форматы сжатия, клиент может определить для каждой директивы свой вес, используя для этого специальную директиву ;q=
в которой указать численное значение веса. Например, клиент отправляет запрос со следующим заголовком:
Accept-Encoding: gzip;q=1, deflate;q=0.8, br;q=0.1
Для формата gzip
установлен самый большой вес (1
). Если gzip
поддерживается сервером, то именно этот формат будет выбран для сжатия, даже в том случае, если сервер поддерживает другие форматы. Если gzip
не поддерживается, то проверяется следующая директива с наибольшим весом из оставшихся и так далее. ASP.NET Core поддерживает использование весов в заголовке Accept-Encoding
.
Реализация сжатия ответов в ASP.NET Core
Как и в большинстве других случаев использования тех или иных возможностей платформы ASP.NET Core, для использования сжатия ответов в приложении необходимо добавить соответствующий сервис и компонент middleware. Например, подключим сжатие ответов в новом приложении ASP.NET Core Web API. Для этого откроем файл Program.cs и добавим в него следующие вызовы методов расширения
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddResponseCaching(); //добавляем кэширование ответов builder.Services.AddResponseCompression();//добавляем сжатие ответов var app = builder.Build(); app.UseResponseCompression();//добавляем сжатие ответов app.UseResponseCaching(); app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
Здесь мы подключили сразу и сервисы кэширования ответов и сервисы сжатия. Следует отметить, что компонент middleware для сжатия ответов должен располагаться в конвейере обработки запросов выше, чем любой другой компонент, для которого требуются сжатые данные. Теперь добавим для действия Get()
контроллера атрибут [ResponseCache]
для кэширования ответов
[HttpGet] [ResponseCache(Duration =120)] public IEnumerable<WeatherForecast> Get() { //код действия }
и добавим в http-файл проекта следующий запрос:
GET {{WebApplication11_HostAddress}}/weatherforecast/ Accept: application/json Accept-Encoding: gzip
Если сейчас попробовать выполнить этот запрос, то несмотря на то, что мы подключили необходимые сервисы сжатия ответов, а клиент отправил запрос с заголовком Accept-Encoding
. Для того, чтобы сжатие заработало, необходимо немного разобраться с настройками сжатия.
Настройки сжатия по умолчанию
На данный момент мы подключили сжатие ответов «как есть», то есть с настройками по умолчанию, которые в ASP.NET Core 9.0 выглядят следующим образом:
- Поддерживаемые форматы сжатия: Brotli (
br
) и Gzip (gzip
). По умолчанию используется сжатие Brotli, когда такой формат поддерживается клиентом. Для сжатия используется самый быстрый уровень. - Запросы по протоколу HTTPS по умолчанию не сжимаются в целях безопасности
- Сжимаются только данные, имеющие следующие MIME-типы:
- text/plain,
- text/css,
- application/javascript,
- text/javascript,
- text/html,
- application/xml,
- text/xml,
- application/json,
- text/json,
- application/wasm,
Таким образом, сейчас наше приложение удовлетворяет двум из трех настроек по умолчанию – поддерживается формат Gzip, MIME-тип содержимого application/json, но, при этом, запросы идут по протоколу https и у нас даже в конвейер обработки запросов включен компонент для автоматического перенаправления запросов с http на https:
app.UseHttpsRedirection();
Вот именно по этой причине (использование https) мы и получили несжатый ответ. Однако, это совсем не означает того, что настройки по умолчанию не могут быть изменены.
Настройка сжатия ответов
Чтобы настроить сервис сжатия ответов, мы можем передать в метод AddResponseCompression()
делегат Action< ResponseCompressionOptions>
. Класс ResponseCompressionOptions
определяет настройки сжатия ответов и содержит следующие свойства
Название | Тип | Описание |
EnableForHttps |
bool |
Определяет возможность сжатия ответов, отправляемых по протоколу https. По умолчанию равно false |
ExcludedMimeTypes |
IEnumerable<string> |
Коллекция mime-типов данных, которые не должны сжиматься |
MimeTypes |
IEnumerable<String> |
Коллекция mime-типов данные, которые должны сжиматься |
Providers |
CompressionProviderCollection |
Коллекция провайдеров сжатия данных |
Таким образом, мы можем определить в своем приложении следующие настройки сжатия
builder.Services.AddResponseCompression(config => { config.EnableForHttps = true; config.ExcludedMimeTypes = ["text/xml"]; config.MimeTypes = ["application/json"]; });
Здесь мы включили сжатие данных, передаваемых по https, исключили из сжатия данные в формате XML и включили сжатие JSON. Особое внимание здесь стоит обратить на последнюю настройку:
config.MimeTypes = ["application/json"];
она переопределяет список сжимаемых данных по умолчанию. То есть теперь сжиматься будет только JSON.
То же самое относится и к свойству Providers
– явное указание провайдера сжатия переопределяет весь список провайдеров. Например, как было сказано выше, по умолчанию поддерживаются два формата сжатия – это gzip (провайдер GzipCompressionProvider
) и Brotli (провайдер BrotliCompressionProvider
) и, если мы настроим сжатие ответов следующим образом
builder.Services.AddResponseCompression(config => { config.EnableForHttps = true; config.ExcludedMimeTypes = ["text/xml"]; config.MimeTypes = ["application/json"]; config.Providers.Add<GzipCompressionProvider>(); });
то наше приложение будет поддерживать сжатие только в формат Gzip. Теперь запустим приложение и проверим выполнение запроса
Как можно видеть по рисунку, ответ пришел в виде «кракозябр». Это нам говорит о том, что ответ был действительно сжат с использованием указанного в заголовках сервера формата
А наличие заголовка Vary: Accept-Encoding
говорит нам о том, что кэшированные данные сжатого и не сжатого ответа различаются.
Настройка провайдеров сжатия
По умолчанию ASP.NET Core настраивает все провайдеры сжатия на самый быстрый режим работы. Однако, не всегда время сжатия ответа играет наиболее важную роль при передаче данных. Нам может потребоваться повысить качество сжатия в ущерб времени выполнения операции. Для этого мы можем определить для каждого используемого поставщика сжатия свои настройки, используя метод расширения ISrviceCollection.Configure()
. Например, определим настройки для провайдера сжатия Gzip
builder.Services.Configure<GzipCompressionProviderOptions>(options => { options.Level = System.IO.Compression.CompressionLevel.SmallestSize; });
Здесь мы указали в качестве универсального параметра метода Configure()
класс GzipCompressionProviderOptions
, который определяет настройки сжатия ответов в Gzip и содержит всего одно свойство Level
– уровень сжатия. В данном случае мы установили уровень сжатия, при котором достигается наименьший размер передаваемых данных.
И здесь стоит ещё раз отметить, что не всегда включение сжатия данных ответа гарантирует меньший объем передаваемых данных так как за счет добавления заголовков формата и другой служебной информации в тело ответа и без того небольшого размера ответ может увеличится в размере. В данном случае сжатие не только не помогает, но даже немного мешает. Однако при более-менее серьезных объемах передаваемых данных эффект, конечно же, будет и, возможно, значительный.
Использование собственных провайдеров сжатия
При необходимости мы можем расширить перечень поддерживаемых провайдеров сжатия, разработав свой собственный провайдер. Класс, определяющий пользовательский провайдер сжатия должен реализовывать интерфейс ICompressionProvider
, который определяет два свойства
Название | Тип | Описание |
EncodingName |
string |
значение, которое используется в заголовках Accept-Encoding и Content-Encoding |
SupportsFlush |
bool |
Указывает, поддерживает ли данный поставщик методы Flush и FlushAsync . |
И один метод
Stream CreateStream(Stream outputStream);
который возвращает поток после сжатия данных. Например, по умолчанию в ASP.NET Core не поддерживается такой формат сжатия как Deflate
, хотя сам формат довольно часто используется в Сети. Давайте разработаем для него свой провайдер сжатия. Добавим в проект новую папку, например, Providers и разместим в ней файл со следующим классом
using Microsoft.AspNetCore.ResponseCompression; using System.IO.Compression; namespace WebApplication11.Providers { public class DeflateCompressionProvider : ICompressionProvider { public string EncodingName => "deflate"; public bool SupportsFlush => true; public Stream CreateStream(Stream outputStream) { return new DeflateStream(outputStream, CompressionLevel.SmallestSize); } } }
Всё, что мы здесь сделали – это реализовали все члены интерфейса, используя стандартную библиотеку для сжатия данных в формат deflate
. Теперь мы можем подключить этот провайдер в нашем приложении
builder.Services.AddResponseCompression(config => { config.EnableForHttps = true; config.ExcludedMimeTypes = ["text/xml"]; config.MimeTypes = ["application/json"]; config.Providers.Add<GzipCompressionProvider>(); config.Providers.Add<DeflateCompressionProvider>(); //подключаем свой провайдер сжатия });
И использовать его при выполнении запросов к серверу
GET {{WebApplication11_HostAddress}}/weatherforecast/ Accept: application/json Accept-Encoding: deflate;q=1, gzip;q=0.5
Здесь мы указали в заголовке Accept-Encoding
веса для каждого формата и так как сервер поддерживает теперь и Gzip
и Deflate
, должен выбрать для сжатия именно наш провайдер. Результат выполнения запроса представлен на рисунке ниже:
Итого
Для ускорения передачи данных по Сети мы можем использовать сжатие ответов (совместно с кэшированием или без него). Для того, чтобы сжатие ответов работало, мы должны подключить сервис сжатия и обязательно его настроить на сжатие при передаче данных по HTTPS, если этот протокол используется в вашем приложении.