HttpRequestMessage.Version と VersionPolicy について
.NET の HttpRequestMessage
には Version
と VersionPolicy
というプロパティーがあります。これらがどういうものなのか少し調べたのでメモ程度にまとめておきます。
まとめ
.NET の System.Net.Http.HttpRequestMessage
の Version
と VersionPolicy
のデフォルト値は .NET 9 と 10 の時点ではそれぞれ HttpVersion.Version11
と RequestVersionPolicy.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) を行いたいのであれば
Version
にHttpVersion.Version20
をRequestVersionPolicy
プロパティーに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 はどのような時に指定するのか
デフォルトの Version
は Version11
(HTTP/1.1) であり、その上で VersionPolicy
のデフォルトは RequestVersionOrLower
となっています。つまりデフォルト状態だと HTTP/1.1 以上にはならないので HTTP/2 以上を使用してほしい場合には Version20
を明示的に指定する必要があります。
ただし VersionPolicy
の設定値によって上位バージョンになったり、逆に下がったりとなるので単に Version
プロパティーをしたからと言ってそのバージョンになるわけではないというのがややこしいポイントです。
VersionPolicy はどのような時に指定するのか
一番多いケースは HTTP/2 を非 HTTPS (h2c) を使用したいときはダウングレードしては困るパターンです。特に gRPC は HTTP/2 の機能を必要とするのでそれに該当します。この場合は Version
に Version20
を指定した上で RequestVersionOrHigher
または RequestVersionExact
を指定することで HTTP/2 を強制できます。(.NET の HTTP/3 での接続には TLS 必須なので実質 HTTP/2 のみ)
他には HTTP/2 を使えるのであれば使ってほしいケースで RequestVersionOrHigher
を指定することも考えられますが Version
を指定することでも現状あまり違いはありません。(HTTP/1.1 かそれ以上バージョン or HTTP/2 かそれ以下のバージョンのどちらかの組み合わせになる)
参考: HttpCilent の DefaultRequestVersion と DefaultVersionPolicy
HttpClient には DefaultRequestVersion
と DefaultVersionPolicy
というプロパティーが存在しています。これは GetAsync
や PostAsync
を呼び出したときに内部で作成している HttpRequestMessage
の Version
と VersionPolicy
に何をセットするかを設定できるものです。デフォルト値は 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 からなのでここから効果を持ちます。
この時点では 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