HttpClient
でPOSTやPUTのような送信を行う場合に、Bodyで送信するコンテンツをGZip圧縮したいと思ったのですが、意外とすぐに使えるサンプルコードが見当たりませんでした。
とりあえず動くコードは書けたので、メモしておきたいと思います。Bodyで送信するコンテンツは、抽象クラスであるHttpContent
で扱うのですが、単純な文字列の場合はStringContent
、ストリームの場合はStreamContent
など用意されている具象クラスを利用できることがあります。
が、GZip圧縮できるクラスはないようなので、自分でHttpContentを継承する必要がありそうです。主に必要なのは、SerializeToStreamAsync
で引数のStreamにGZip圧縮したコンテンツを書き込む処理です。また、実装する際には送信するコンテンツをコンストラクタなどで受け取る必要があります。今回送信するコンテンツは文字列結合で生成しているので、ZStringのUtf8ValueStringBuilder
で渡してストリームに書き込むことにしました。
というわけでこのように実装しました。
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
はコンテンツのサイズが低コストでわかる場合のみ正しい数値を返すので、今回は計算しないように実装しています。
利用するときはこのようになります。
// サンプルなので都度生成しているが、HttpClientのインスタンス生成は適切に。 var client = new HttpClient(); var endpoint = "https://example.com/log/v1"; using var sb = ZString.CreateUtf8StringBuilder(); sb.Append("[{\"logs\": ["); // さらにsbに追記 sb.Append("]}]"); var res = await client.PostAsync(Endpoint, new GZipZStringContent(sb)); res.EnsureSuccessStatusCode();
なお、処理全体としては最近公開したライブラリのこのクラスに記述しています。