こんにちは。ベトナムからエンジニアCCです。
2015末から全て主要なブラウザがhttp2をサポートになりました。
2年前http2を運用しようと思いましたが、色んな疑問がありました。

http2のServer Pushの問題

  • railsがhttp2を対応しますか?
  • nginxがhttp2を対応しますか?
  • メリットってありますか?(Server Pushとか利用できますか?)
    など

特にServer Pushですね。
例えば、nginxじゃなくてh2oを切り替えてmagic的にserver pushを利用できるかと思っていました。
そう出来ればrails側はhttp2を対応しなくてもhttp2のメリットがあるかと思いました。
普通のリクエストは基本には5つのフェーズがあります。

Screen Shot 2019-12-16 at 12.53.54.png

Proxy(nginxやh2o)はhtmlを受けて、処理して以下の状態になります。

Screen Shot 2019-12-16 at 12.56.24.png

で、各フェーズのタイミングを確認したらあまり効果がないかと思っていました。
Screen Shot 2019-12-16 at 12.59.00.png

Download時間だけ節約できましたが、日本のインターネットスピードはめっちゃ早いので、結局何もメリットできない。

理想的にはserver pushはproxyサーバーからじゃなくてアプリから送るのほうがいいではないですか。

(こんな感じ👇
Screen Shot 2019-12-16 at 13.04.00.png

そうすと別の問題起こってしまいました。
まずはrailsはhttp2を対応必要。
例えrailsはhttp2を対応しても以下のような形が必要と思いました。

Screen Shot 2019-12-16 at 13.02.10.png

http2自体はsslが必要ので、↑のようアーキテックはめっちゃ複雑になっています。

Rails 5.2

Rails 5.2から HTTP/2 Early Hints を対応くれました 🎉
https://railsguides.jp/5_2_release_notes.html

具体的にはどんな対応だろか?
http/2 early hints対応プルリク: https://github.com/rails/rails/pull/30744
→ 簡単説明すると、javascript_include_tagstylesheet_link_tagを実行する瞬間に
Link: </style.css>; as=style; rel=preload
のheaderを反します。
これ自体はhttp1だけら以下の設定で動けます 🎉

Screen Shot 2019-12-16 at 13.21.32.png

やってみましょう

ということで実際やってみました。
以下のdocker-composeで試しました。

version: "3.7"

services:
  app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - ./:/app
      - ./tmp/root/:/root # save .bash_history
    stdin_open: true
    tty: true
  nginx:
    image: nginx:latest
    links:
      - app
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./docker/nginx/certs/:/etc/ssl/certs:ro

nginx.conf

    server {
        listen                  443 ssl http2;
        ssl_certificate         /etc/ssl/certs/localhost.crt;
        ssl_certificate_key     /etc/ssl/certs/localhost.key;
        http2_push_preload      on;
        add_header Strict-Transport-Security "max-age=31536000";

        location / {
            proxy_set_header Host $http_host;
            proxy_pass_request_body on;
            proxy_pass_request_headers on;
            proxy_pass http://upstream;
        }
    }

chromeのdevtoolでNetworkタブを見てhttp2を見えました 🎉🎉🎉
ただ、最初のページはスタチックページなのでjavascript_include_tagstylesheet_link_tagを実行されないです。→ Server pushまだ為できない。
→ controllerを作成して、見たら。。。

ERR_SPDY_PROTOCOL_ERROR エラー出た 😱😱😱😱

以下のurlでみてエラーログを収集して確認しました
chrome://net-export/

エラーのログ:

{"description":"DATA received before headers.","net_error":"ERR_HTTP2_PROTOCOL_ERROR","stream_id":3}

なぜ?この記事によって1.13.9からhttp2が対応くれるはずなのに、↑のエラー出てしました
https://www.nginx.com/blog/nginx-1-13-9-http2-server-push/

nginx version: nginx/1.17.6

そこで諦めしました。h2oを使って試しました。

h2o.conf

h2o:
    image: lkwg82/h2o-http2-server:latest
    links:
      - app
    ports:
      - "443:1443"
      - "80:8080"
    restart: always
    volumes:
      - ./docker/h2o/h2o.conf:/home/h2o/h2o.conf:ro
      - ./docker/certs/:/etc/ssl/certs:ro

h2o.jpg
動けました 🎉🎉🎉

結論

  • 違いドメインは対応くれないからassetsとhtmlは別originだとpushできません
  • 直接htmlを利用しなくて必ずjavascript_include_tagやstylesheet_link_tagを書かないとpushできません
  • controllerで遅い処理があったらあまり効果がない、queryとかはviews側で発生したら効果あります
  • キャッシュされたリソースがメリットがありません
  • なぜかNGINX動きません
  • jsとcssしか対応していません(画像やfontまだ対応されていません)
  • @importとかで書いたらpushできません
  • 本番ではダイナミックCSSやダイナミックJavascriptがあったらメリットがあるが、普通はあまりないです。
  • 開発環境で使ったらいいだと思います。