HttpClient
でPOSTやPUTのような送信を行う場合に、Bodyで送信するコンテンツをGZip圧縮したいと思ったのですが、意外とすぐに使えるサンプルコードが見当たりませんでした。
docs.microsoft.com
とりあえず動くコードは書けたので、メモしておきたいと思います。Bodyで送信するコンテンツは、抽象クラスであるHttpContent
で扱うのですが、単純な文字列の場合はStringContent
、ストリームの場合はStreamContent
など用意されている具象クラスを利用できることがあります。
docs.microsoft.com
docs.microsoft.com
が、GZip圧縮できるクラスはないようなので、自分でHttpContentを継承する必要がありそうです。主に必要なのは、SerializeToStreamAsync
で引数のStreamにGZip圧縮したコンテンツを書き込む処理です。また、実装する際には送信するコンテンツをコンストラクタなどで受け取る必要があります。今回送信するコンテンツは文字列結合で生成しているので、ZStringのUtf8ValueStringBuilder
で渡してストリームに書き込むことにしました。
github.com
というわけでこのように実装しました。
using Cysharp.Text;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
public class GZipZStringContent : HttpContent
{
private readonly Utf8ValueStringBuilder sb;
public GZipZStringContent(Utf8ValueStringBuilder sb)
{
Headers.TryAddWithoutValidation("Content-Type", "application/gzip");
Headers.ContentEncoding.Add("gzip");
this.sb = sb;
}
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
var gzipStream = new GZipStream(stream, CompressionMode.Compress, leaveOpen: true);
return sb.WriteToAsync(gzipStream).ContinueWith(_ =>
{
gzipStream?.Dispose();
});
}
protected override bool TryComputeLength(out long length)
{
length = -1L;
return false;
}
}
SerializeToStreamAsync
メソッドでは、Utf8ValueStringBuilder のWriteToAsync
メソッドを使って生成したGZipSteam
にコンテンツを書き込んでいます。また、完了したあとに、GZipStreamのDispose
が呼び出されるようにしています。ZStringではない別のもので受け渡したい場合は、コンストラクタで受け取ったものをGZipStreamに書き込むように処理を変えれば問題ないはずです。
また、コンストラクタではContent-Type
とContent-Encoding
ヘッダーを設定しています。TryComputeLength
はコンテンツのサイズが低コストでわかる場合のみ正しい数値を返すので、今回は計算しないように実装しています。
docs.microsoft.com
利用するときはこのようになります。
var client = new HttpClient();
var endpoint = "https://example.com/log/v1";
using var sb = ZString.CreateUtf8StringBuilder();
sb.Append("[{\"logs\": [");
sb.Append("]}]");
var res = await client.PostAsync(Endpoint, new GZipZStringContent(sb));
res.EnsureSuccessStatusCode();
なお、処理全体としては最近公開したライブラリのこのクラスに記述しています。
github.com