🤝

HttpRequestMessage.Version と VersionPolicy について

に公開

.NET の HttpRequestMessage には VersionVersionPolicy というプロパティーがあります。これらがどういうものなのか少し調べたのでメモ程度にまとめておきます。

まとめ

.NET の System.Net.Http.HttpRequestMessageVersionVersionPolicy のデフォルト値は .NET 9 と 10 の時点ではそれぞれ HttpVersion.Version11RequestVersionPolicy.RequestVersionOrLower です。

これを踏まえて選択される HTTP バージョンに関しては次のようになります:

  • デフォルトではリクエストを送信するとサーバーの HTTP/2, HTTP/3 対応状況に関わらず HTTP/1.1 になる
  • サーバーが HTTP/2 対応なら HTTP/2 で接続してほしいのであれば次のいずれかを設定
    • RequestVersionPolicy プロパティーに RequestVersionOrHigher を設定
    • Version プロパティーに HttpVersion.Version20 または HttpVersion.Version30 を設定
  • HTTP/2 over cleartext (prior knowledge) を行いたいのであれば VersionHttpVersion.Version20RequestVersionPolicy プロパティーに RequestVersionOrHigher を設定

Version プロパティー

Version プロパティーは .NET 5 以降では VersionPolicy プロパティーで設定された HTTP バージョンのネゴシエーションの基準となるバージョンです。ここで指定したバージョンでリクエストするとは限らない点に注意が必要です。

VersionPolicy プロパティー

.NET 5 で追加されたプロパティーです。子のプロパティーは HTTP 接続を確立する上で Version プロパティーで指定された HTTP バージョンにどう寄せるかといった振る舞い/ポリシーを指定します。

VersionPolicy プロパティーに指定する VersionPolicy 列挙体には以下の3つの値が定義されています。

  • RequestVersionOrLower (0, デフォルト)
  • RequestVersionOrHigher (1)
  • RequestVersionExact (2)

RequestVersionOrLower

RequestVersionOrLower は VersionPolicy のデフォルト値で「指定したバージョンかそれ以下」を要求します。つまり HttpVersion.Version20 が指定されている場合には HTTP/2 または HTTP/1.1 が選択されます。

ドキュメントでは次のように説明されています。

要求されたバージョンを使用するか、下位のものにダウングレードします。 これが既定の動作です。

要求されたバージョン (ALPN (H2) でネゴシエートされるか Alt-Svc (H3) でアドバタイズされたもの) がサーバーでサポートされているときに、セキュリティで保護された接続が要求された場合、結果は Version になります。 それ以外の場合、バージョンは HTTP/1.1 にダウングレードされます。 このオプションでは、事前ネゴシエートされたクリア テキスト接続 (例: H2C) の使用は許可されません。

この設定値では TLS の有無で挙動が変わります。例えば対象となるサーバーに HTTPS で接続する場合には HTTP/2 が使えるかどうか ALPN で判断し確立します。一方、非 HTTPS では ALPN によるネゴシエーションを行えないので必然的に HTTP/1.1 に決定されます。この場合では Version = HttpVersion.Version20 を指定していても HTTP/2 で接続を確立できない、といった形になります。

RequestVersionOrHigher

RequestVersionOrHigher は「指定したバージョンかそれ以上」を要求します。つまり Version = HttpVersion.Version11 (HTTP/1.1) を指定していても、サーバーが HTTP/2 に対応しているのであれば接続は HTTP/2 で確立されます。

ドキュメントでは次のように説明されています。

利用可能な最新のバージョンを使用してください。要求されたバージョンのみにダウングレードし、それより下にはしないでください。

要求されたバージョン (ALPN (H2) でネゴシエートされるか Alt-Svc (H3) でアドバタイズされたもの) よりも新しいバージョンがサーバーでサポートされているときに、セキュリティで保護された接続が要求された場合、結果は利用可能な最新のバージョンになります。 それ以外の場合、バージョンは Version にダウングレードされます。 このオプションでは、要求されたバージョン用に事前ネゴシエートされたクリア テキスト接続の使用が許可されますが、それより新しいバージョンに対しては許可されません。

これは「指定したバージョンを下回らないこと」を要求するので非 HTTPS な HTTP/2 (h2c) によるリクエストの場合でも HTTP/1.1 にダウングレードするといったことは行われないことになります。

RequestVersionExact

RequestVersionExact は「指定したバージョン」で接続することを要求します。これは下がりもしないし上がりもしない、といった挙動になります。

ドキュメントでは次のように説明されています。

要求されたバージョンのみを使用してください。

このオプションでは、要求されたバージョン用に事前ネゴシエートされたクリア テキスト接続の使用が許可されます。

例えば HTTPS + Version11 で HTTP/2 対応サーバーに接続した場合でも HTTP/2 にはならないですし、HTTP + Version20 であっても HTTP/1.1 にダウングレードされたりしません。

Version はどのような時に指定するのか

デフォルトの VersionVersion11 (HTTP/1.1) であり、その上で VersionPolicy のデフォルトは RequestVersionOrLower となっています。つまりデフォルト状態だと HTTP/1.1 以上にはならないので HTTP/2 以上を使用してほしい場合には Version20 を明示的に指定する必要があります。

ただし VersionPolicy の設定値によって上位バージョンになったり、逆に下がったりとなるので単に Version プロパティーをしたからと言ってそのバージョンになるわけではないというのがややこしいポイントです。

VersionPolicy はどのような時に指定するのか

一番多いケースは HTTP/2 を非 HTTPS (h2c) を使用したいときはダウングレードしては困るパターンです。特に gRPC は HTTP/2 の機能を必要とするのでそれに該当します。この場合は VersionVersion20 を指定した上で RequestVersionOrHigher または RequestVersionExact を指定することで HTTP/2 を強制できます。(.NET の HTTP/3 での接続には TLS 必須なので実質 HTTP/2 のみ)

他には HTTP/2 を使えるのであれば使ってほしいケースで RequestVersionOrHigher を指定することも考えられますが Version を指定することでも現状あまり違いはありません。(HTTP/1.1 かそれ以上バージョン or HTTP/2 かそれ以下のバージョンのどちらかの組み合わせになる)

参考: HttpCilent の DefaultRequestVersion と DefaultVersionPolicy

HttpClient には DefaultRequestVersionDefaultVersionPolicy というプロパティーが存在しています。これは GetAsyncPostAsync を呼び出したときに内部で作成している HttpRequestMessageVersionVersionPolicy に何をセットするかを設定できるものです。デフォルト値は HttpRequestMessage のデフォルト値と同じ値となっています。

参考: .NET 5 以前 (.NET Core) ではどうだったのか

VersionPolicy プロパティー(とその列挙体)は .NET 5 で導入された API ですが、それ以前の .NET Core では HTTP バージョン周りはどうなっていたのかについても少し触れておきます。

.NET Core 2.1, 2.2

.NET Core 2.1 と 2.2 では Version のデフォルト値が Version20 (HTTP/2) でしたが、そもそもこの時点では .NET Core のクライアントは HTTP/2 をサポートしていませんでした。

.NET Core 3.1

.NET Core 3.1 では Version のデフォルト値が Version11 (HTTP/1.1) に戻りました。クライアントにおける HTTP/2 のサポートは .NET Core 3.0 からなのでここから効果を持ちます。

https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-core-3-0#http2-support

この時点では VersionPolicy がありませんので RequestVersionOrLower 相当がデフォルトで、 HTTP/2 必須な gRPC (grpc-dotnet) では Version = HttpVersion.Version20 を指定しています。

これで HTTPS な HTTP/2 に接続した場合には正しく HTTP/2 で確立できますが、そのままでは非 HTTP に接続した際にダウングレードされてしまいます。そこで非 HTTPS を利用したい場合には System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport という AppSwitch をオンにすることで Version20 かつ 非HTTP で接続すると RequestVersionExact 相当の挙動となるようになっていました。

Discussion