Распаковка запросов в ASP.NET Core

Примечание.

Это не последняя версия этой статьи. В текущей версии см. версию .NET 10 этой статьи.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущей версии см. версию .NET 10 этой статьи.

Автор: Дэвид Эйкер (David Acker)

Промежуточное ПО для распаковки запросов:

  • позволяет конечным точкам API принимать запросы со сжатым содержимым;
  • использует заголовок HTTP Content-Encoding, чтобы автоматически обнаруживать и распаковывать запросы, содержащие сжатое содержимое;
  • устраняет необходимость писать код для обработки сжатых запросов.

Если значение заголовка Content-Encoding в запросе соответствует одному из доступных поставщиков декомпрессии, промежуточное ПО:

  • Использует соответствующий поставщик, чтобы обернуть HttpRequest.Body в подходящий поток декомпрессии.
  • удаляет заголовок Content-Encoding, так как текст запроса больше не сжат.

Запросы, не содержащие заголовок Content-Encoding, игнорируются ПО промежуточного слоя для распаковки запросов.

Распаковка:

  • Происходит при чтении текста запроса. Это значит, что при привязке модели распаковка осуществляется в конечной точке. Текст запроса не декомпрессируется с нетерпением.
  • При попытке прочитать распакованное тело запроса при наличии недопустимых сжатых данных для указанного Content-Encoding выдается исключение. Brotli может вызывать исключение System.InvalidOperationException: Decoder ran into invalid data., а Deflate и GZip могут вызывать исключение System.IO.InvalidDataException: The archive entry was compressed using an unsupported compression method.
  • При попытке прочитать распакованное тело запроса при наличии недопустимых сжатых данных для указанного Content-Encoding выдается исключение. Brotli может вызывать System.InvalidOperationException: Decoder ran into invalid data. Deflate, GZip и Zstandard могут вызывать System.IO.InvalidDataException: The archive entry was compressed using an unsupported compression method.

Если ПО промежуточного слоя обнаруживает запрос со сжатым содержимым, но его не удается распаковать, запрос передается следующему делегату в конвейере. Например, запрос со значением неподдерживаемого Content-Encoding заголовка или несколькими Content-Encoding значениями заголовков передается следующему делегату в конвейере.

Настройка

Следующий код использует AddRequestDecompression(IServiceCollection) и UseRequestDecompression, чтобы включить распаковку запроса для типов по умолчаниюContent-Encoding:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRequestDecompression();

var app = builder.Build();

app.UseRequestDecompression();

app.MapPost("/", (HttpRequest request) => Results.Stream(request.Body));

app.Run();

Поставщики распаковки по умолчанию

Поддерживаемые по умолчанию посредником распаковки запросов значения заголовка Content-Encoding перечислены в следующей таблице:

Content-Encoding значения заголовков Описание
br Формат сжатых данных Brotli
deflate Формат сжатых данных Deflate
gzip Формат файлов gzip
Content-Encoding значения заголовков Описание
br Формат сжатых данных Brotli
deflate Формат сжатых данных Deflate
gzip Формат файлов gzip
zstd Формат сжатых данных Zstandard

Настраиваемые поставщики распаковки

Поддержку пользовательских кодировок можно добавлять, создавая настраиваемые классы поставщиков распаковки, реализующие IDecompressionProvider:

public class CustomDecompressionProvider : IDecompressionProvider
{
    public Stream GetDecompressionStream(Stream stream)
    {
        // Perform custom decompression logic here
        return stream;
    }
}

Настраиваемые поставщики распаковки регистрируются с использованием RequestDecompressionOptions вместе с соответствующими значениями заголовков Content-Encoding:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRequestDecompression(options =>
{
    options.DecompressionProviders.Add("custom", new CustomDecompressionProvider());
});

var app = builder.Build();

app.UseRequestDecompression();

app.MapPost("/", (HttpRequest request) => Results.Stream(request.Body));

app.Run();

Предельный размер запросов

Чтобы защититься от zip-бомб или декомпрессийных бомб:

  • максимальный размер текста распакованного запроса ограничивается согласно лимиту на размер текста запроса в конечной точке или на сервере;
  • если из потока текста распаковываемого запроса считывается количество байт, превышающее ограничение, создается исключение InvalidOperationException, чтобы предотвратить чтение дополнительных байтов из потока.

Максимальный размер запроса для конечной точки задается в порядке приоритета следующим образом:

  1. IRequestSizeLimitMetadata.MaxRequestBodySize, например RequestSizeLimitAttribute или DisableRequestSizeLimitAttribute для конечных точек MVC.
  2. Глобальное серверное ограничение размера — IHttpMaxRequestBodySizeFeature.MaxRequestBodySize. MaxRequestBodySize можно переопределить для каждого запроса с помощью IHttpMaxRequestBodySizeFeature.MaxRequestBodySize, но по умолчанию используется ограничение, настроенное для реализации веб-сервера.
Реализация веб-сервера MaxRequestBodySizeКонфигурация
HTTP.sys HttpSysOptions.MaxRequestBodySize
IIS IISServerOptions.MaxRequestBodySize
Kestrel KestrelServerLimits.MaxRequestBodySize

Предупреждение

Отключение лимита для размера текста запроса создает риск для безопасности, связанный с неконтролируемым потреблением ресурсов, особенно если текст запроса буферизуется. Убедитесь, что приняты меры по безопасности, чтобы снизить риск атак типа отказ в обслуживании (DoS).

Дополнительные ресурсы