4月 192019
 
Pocket

.NET Core 3.0 Preview 4 で HttpClient の HTTP/2 サポートが追加されたようですね。
Announcing .NET Core 3 Preview 4 | .NET Blog

今までの .NET Standard では SslStream 自体が HTTP/2 の事実上必須な機能(ALPN)に対応しておらず、.NET Standard 2.1 でようやくサポートされる見込みだったため、どうやらちゃんとくるようで一安心というところです。
(HttpClient についてはこれのせいかは不明ですが…… SocketsHttpHandler が HTTP/2 対応していなかったのは確か)

しかしながら .NET Framework 4.8 の方は .NET Standard 2.0 止まりになることになっているので、HTTP/2 サポートは今後あるのかどうかすら怪しいですね……

Given many of the API additions in .NET Standard 2.1 require runtime changes in order to be meaningful, .NET Framework 4.8 will remain on .NET Standard 2.0 rather than implement .NET Standard 2.1.
Announcing .NET Standard 2.1 | .NET Blog

とはいえ .NET Standard はあくまでも API 標準を定めただけで実装は各プラットフォーム依存ですから、動くようになる可能性はあるかも?

とりあえず .NET Framework は 4.8 の時点では HttpClient + HttpClientHandler では動作しそうにありません。
BCL 以外でなら WinHttpHandler を使い、HttpRequestMessage のバージョンを明示的に指定することで、利用できます(Win10 1607↑ + .NET FW 4.6↑ に限る)。
参考: How to make the .net HttpClient use http 2.0?

実は Core 2.0 までも Windows 環境に限れば WinHttpHandler に丸投げしていたようで、HttpClient で HTTP/2 が動作します。
Core 2.1 以降 SocketsHttpHandler に移行し、環境依存を減らした為、HTTP/2 に一時的に非対応となっていたようです。

HttpClient の HTTP/2 対応

  • .NET Framework
    • HttpClientHandler では利用不可
    • WinHttpHandler (要NuGet & Win10 1607)を利用すれば利用可
  • .NET Core
    • ~2.0 : Win10 1607~のみ利用可?
      • WinHttpHandler を使ってる模様
    • 2.1~2.2 : 利用不可
      • SocketsHttpHandler に移行したため
      • ただし WinHttpHandler を利用するよう構成すれば Windows では利用可
    • 3.0~ : 利用可
      • SocketsHttpHandler の HTTP/2 対応?

HTTP/2 に事実上必須な ALPN

さて、では今まで何が足りなかったかというと、ALPN サポートです。
ALPN とは、TLS 接続開始時に TLS 内で用いるプロトコルをクライアント・サーバー間で交渉する TLS の拡張機能です。

HTTP/2 を開始するにあたっては、クライアントとサーバー間で HTTP/2 を使用するという同意(プロトコルネゴシエーション)が必要になります。
その方法は 3 種類ありますが、ALPN はそのうちの1つとなります。

  1. http の場合、HTTP/1.1 で開始し Upgrade ヘッダによるプロトコルアップグレードを用いる
  2. https の場合、TLS 拡張機能の ALPNを用いる
  3. Alt-Svc ヘッダなどで、事前に HTTP/2 対応していることを知っておく(平文でのみ使用可)

この内、TLS を用いる場合は 2 の ALPN しか用いることができません。

現状、Web ブラウザ等クライアント製品群の HTTP/2 サポートは TLS が必須となっていることが殆どで、平文の大半はサーバーサイドでしか用いられていないと予想できます。
よって TLS 対応していない場合は、用途が限定的な製品でもない限りは胸を張って HTTP/2 サポートしているとはいい難いでしょう。

つまり HTTP/2 をちゃんとサポートするためには ALPN 対応は必須なわけです。

SslStream の ALPN サポート

SslStream ではクライアント側からの TLS ハンドシェイクは AuthenticateAsClientAsync を、サーバー側からは AuthenticateAsServerAsync を用いますが、そこで ALPN のプロトコルを指定するための SslApplicationProtocol を設定できるようにするために新たに作られた SslClientAuthenticationOptions, SslServerAuthenticationOptions を引数とするオーバーロードが追加されました。

また、ハンドシェイク終了後にどのプロトコルが選択されたのかを取得するための NegotiatedApplicationProtocol プロパティも追加されました。

var options = new SslClientAuthenticationOptions
{
    ApplicationProtocols = alpn.ListOfProtocols,
    CertificateRevocationCheckMode = X509RevocationMode.NoCheck,
    EnabledSslProtocols = true,
    TargetHost = "example.com",
};
await this.SslStream.AuthenticateAsClientAsync(options, CancellationToken.None);
if (this.SslStream.NegotiatedApplicationProtocol == SslApplicationProtocol.Http2)
{
    this.ChangeToHttp2();
}

サーバー側も同様に ApplicationProtocols プロパティに選択したプロトコルを指定すれば良いんですが、クライアントが送信してくるプロトコルリストはどうやって取得すれば良いんでしょうね(あまり詳しく調べてない)。
私は諸事情で TLS を解析して ALPN データを取得するコードを書きましたけども、それなりに面倒くさいです。
今のところは HttpClient を念頭に置いてるようなので、クライアントサイドのことしか考えられてないのかもしれません。
(よく考えたらサーバーサイドの通信は Kestrel や IIS が担うことが多いような)

参考 : Proposed SslStream additions for ALPN · Issue #23177 · dotnet/corefx

 Leave a Reply