しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

IIS 10.0 と ASP.NET 4.6 で HTTP/2 のサーバープッシュが使えるようになっていたので試した

IIS 10.0 で HTTP/2 のサーバープッシュに対応しているか分からないと書きましたが、実際には ASP.NET からサーバープッシュを簡単に行えるようになっていました。

まずは HTTP/2 のサーバープッシュについての参考になる記事を紹介しておきます。

初めてのHTTP/2サーバプッシュ | GREE Engineering
HTTP/2 入門 - Yahoo! JAPAN Tech Blog

要するに、リクエストされたページに必要なリソースを予めクライアントに送信しておくための仕様ですね。ページをパースしてからリソースを取りに行くのと異なり、既にダウンロード済みとなるので、素早く表示することが出来るというからくりです。

ASP.NET 4.6 での対応

ASP.NET 4.6 の API を軽く調べてみると、HttpResponse に PushPromise というメソッドが追加されていることに気が付きました。これは ASP.NET 4.6 の HTTP/2 対応らしいです。

HttpResponse.PushPromise Method (System.Web) | Microsoft Learn

HttpResponse クラスと HttpResponseBase クラスの両方に実装されているので、Web Forms と MVC の両方で問題なく使えるようになっています。

簡単に試したいので、Web Forms を使ってサンプルコードを書いてみました。1 行ですけど。

public partial class Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Response.PushPromise("~/iisstart.png");
    }
}

PushPromise メソッドの引数は仮想パスじゃないとエラーになります。

サーバープッシュ無し

まずは PushPromise メソッドの呼び出しをコメントアウトして試したところ、iistart.png のリクエストでは TTFB が大きな時間を占めていることが分かります。

サーバーからデータを受信し始めるまで時間がかかってます。

サーバープッシュ有り

次に PushPromise メソッドの呼び出しを有効にして試したところ、TTFB がほぼ無くなりました。

Request Sent に時間がかかるようになっていますが、これは純粋にプッシュされた画像のダウンロードに時間がかかっているのかもしれません。

Chrome で HTTP/2 のフレームを確認してみたところ、ちゃんとプッシュされていることが確認出来ます。

レンダリングがブロックされる JavaScript や CSS をプッシュした方が効果が大きい気がします。

ASP.NET MVC から使う

本来なら透過的に静的なリソースをプッシュするような HTTP Module を実装するのが良いと思いましたが、ちょっと手間なので MVC の URL ヘルパーとして実装してみました。

public static string PushContent(this UrlHelper urlHelper, string contentPath)
{
    var context = urlHelper.RequestContext.HttpContext;

    context.Response.PushPromise(contentPath);

    return urlHelper.Content(contentPath);
}

実装としては PushPromise を呼び出す単純なヘルパーです。使い方は Url.Content と同じですが、プッシュしたいコンテンツの時に使うようにするだけで恩恵に与れます。

ブラウザで確認すると、TTFB はとても短くなっているのでプッシュされていることが確認出来ます。

今回は手を抜いて全コンテンツに対してプッシュを使ってみましたが、サーバープッシュを使うとキャッシュが効かなくなるので、CDN で配信可能なものに関しては有効にしない方が良い気がしました。

Firefox でも試してみると、プッシュが効いているからかタイミングすら表示されなかったです。

コンテンツを事前に送り込むというのは、キャッシュされないということを考えると、思っていたよりも使いどころが難しい機能だと感じました。