Сжатие Запросов 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/