銀の光と碧い空

クラウドなインフラとC#なアプリ開発の狭間にいるエンジニアの日々

.NET Standard の HttpClientで送信時のBodyをgzip圧縮したい

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-TypeContent-Encodingヘッダーを設定しています。TryComputeLengthはコンテンツのサイズが低コストでわかる場合のみ正しい数値を返すので、今回は計算しないように実装しています。

docs.microsoft.com

利用するときはこのようになります。

// サンプルなので都度生成しているが、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();

なお、処理全体としては最近公開したライブラリのこのクラスに記述しています。

github.com

NupkgファイルをGiHub PackageにPublishするGitHub Actionを作りました

github.com

というGitHub Actionを公開しました。

作った背景ですが、まずGitHub Actionsの中でGitHub PackageにNuGetパッケージを公開する手順は、Windows限定で昨日の記事に書きました。

tech.tanaka733.net

公開リポジトリならWindowsでも無料なので気にする必要もないのですが、プライベートリポジトリだとLinuxの2倍料金がかかるのでLinuxで対応したいこともあるでしょう。

help.github.com

Linuxの場合、nuget.configファイルで設定しないといけないのは昨日の記事の通りです。この中で、TOKENをyamlの中に直接書くわけにもいかなので、引数で渡した処理しないといけないというのも書いた通りです。bashでXML扱うのも面倒ですが、クロスプラットフォームだとPowerShell Coreもシェルとして利用できるので、これで対応することも可能です。

help.github.com

なのですが、何度も長いスクリプト埋め込むのもいやなので、練習がてらGitHub Actionとして作成することにしました。実装は、安直にnuget.configファイルをプロジェクトルートに配置してから、dotnet nuget pushコマンドを実行しています。

github.com

今、GitHub HackathonでGitHub Actionsを作って公開すると応募できるので、ぜひこのActionを使って、その次は作成してみてください。

githubhackathon.com

NupkgファイルをGiHub PackageにPublishするGitHub Actionを作りました

github.com

というGitHub Actionを公開しました。

作った背景ですが、まずGitHub Actionsの中でGitHub PackageにNuGetパッケージを公開する手順は、Windows限定で昨日の記事に書きました。

tech.tanaka733.net

公開リポジトリならWindowsでも無料なので気にする必要もないのですが、プライベートリポジトリだとLinuxの2倍料金がかかるのでLinuxで対応したいこともあるでしょう。

help.github.com

Linuxの場合、nuget.configファイルで設定しないといけないのは昨日の記事の通りです。この中で、TOKENをyamlの中に直接書くわけにもいかなので、引数で渡した処理しないといけないというのも書いた通りです。bashでXML扱うのも面倒ですが、クロスプラットフォームだとPowerShell Coreもシェルとして利用できるので、これで対応することも可能です。

help.github.com

なのですが、何度も長いスクリプト埋め込むのもいやなので、練習がてらGitHub Actionとして作成することにしました。実装は、安直にnuget.configファイルをプロジェクトルートに配置してから、dotnet nuget pushコマンドを実行しています。

github.com

今、GitHub HackathonでGitHub Actionsを作って公開すると応募できるので、ぜひこのActionを使って、その次は作成してみてください。

githubhackathon.com