TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.75K subscribers
Post #2985 2.2K
День 2483. #ЗаметкиНаПолях
Сжатие Запросов HttpClient с Помощью GZIP
Если вам нужно отправлять большие файлы во внешний API, вы можете решить, что необходимо сжимать их перед отправкой. Однако дело в том, что очевидное решение может привести к проблемам с памятью…

Рассмотрим такой код:
internal async ValueTask<HttpRequestMessage>
CreateRequest(Uri uri, Stream source)
{
var outStream = new MemoryStream();
await using (var zip =
new GZipStream(outStream,
CompressionMode.Compress,
leaveOpen: true))
{
await source.CopyToAsync(zip);
zip.Close();
}
outStream.Position = 0;

var request = new HttpRequestMessage(
HttpMethod.Post, uri)
{
Content = new StreamContent(outStream)
};
request.Content.Headers.ContentEncoding.Add("gzip");
return request;
}


Использовать его можно примерно так:
var uri = new Uri("https://my.api/endpoint");
var file = File.OpenRead("largefile.txt");
var request = await CreateRequest(uri, file);
await httpClient.SendAsync(request);

Обычно этот код просто работает. Однако каждый раз при отправке запроса сжатый gzip-контент создаётся в памяти (благодаря MemoryStream) перед отправкой. И если требуется отправлять много таких запросов, приложение начнёт испытывать нехватку памяти.

Решение в gzip-сжатии «на лету»:
public class GzipContent : HttpContent
{
private readonly Stream _source;

public GzipContent(Stream source)
{
_source = source;
Headers.ContentEncoding.Add("gzip");
}

protected override async Task
SerializeToStreamAsync(
Stream stream,
TransportContext? context)
{
await using var zip =
new GZipStream(stream,
CompressionMode.Compress,
leaveOpen: true);
await _source.CopyToAsync(zip);
}

protected override bool
TryComputeLength(out long length)
{
length = -1;
return false;
}
}

Также переделаем CreateRequest:
internal ValueTask<HttpRequestMessage>
CreateRequest(Uri uri, Stream source)
{
return ValueTask.FromResult(
new HttpRequestMessage(HttpMethod.Post, uri)
{
Content = new GzipContent(source)
});
}

Использование будет точно таким же.

Производительность
Вот мои тесты в .NET10. По времени особой разницы нет, зато версия GzipContent выделяет гораздо меньше памяти. Чем больше файл, тем заметнее разница.
| File | Method| Mean   | Ratio| Allocated| Alc Rat |
|-----:|-------|-------:|-----:|---------:|--------:|
| 7KB| Memory| 633.3us| 1.00| 6.35KB| 1.00|
| 7KB| Gzip | 603.5us| 0.96| 4.77KB| 0.75|
| 200KB| Memory| 8.025ms| 1.00| 508.52KB| 1.000|
| 200KB| Gzip | 7.260ms| 0.90| 4.74KB| 0.009|
| 3MB| Memory| 94.97ms| 1.00| 8189.99KB| 1.000|
| 3MB| Gzip | 86.02ms| 0.91| 4.82KB| 0.001|


PS: .NET API может автоматически распаковывать запросы. Достаточно добавить соответствующее промежуточное ПО:
builder.Services.AddRequestDecompression();

// …

app.UseRequestDecompression();


Источник: https://josef.codes/compress-httpclient-requests-with-gzip-dotnet-core/
  • 👍 18
More from @netdeveloperdiary
  1. Sep 30, 2026Post #3356
  2. Sep 29, 2026Фото 3 (с) Анатолий Кулаков
  3. Sep 29, 2026День 2799. Конференция DotNext 2026. Часть 1 25 и 26 сентября в Москве прошла очередная ко…
  4. Sep 28, 2026День 2798. #Оффтоп Утиная Типизация в C# с Помощью Перехватчиков. Часть 2 Некоторое время…
  5. Sep 27, 2026День 2797. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Окончание Начало Продол…
  6. Sep 26, 2026День 2796. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Продолжение Начало Три…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →