nginx1.9.5のHTTP/2の機能を使ってみた
次世代Web カンファレンスでも話題になっていたHTTP/2でnginxも1.9.5でサポートということで使ってみた。 サポートしたと言ってもnginxのページには以下の注意書きが書かれていた。
- もしアプリでWAFを使っていて、nginxの前にあるならHTTP/2に対応しているか確認し、指定なければnginxの後ろにしてください
- HTTP/2のサーバープッシュはこのリリースではまだサポートされません
- もしssl_prefer_server_ciphersがonに設定されていて、ssl_ciphersのブラックリストに乗っているものを使っている場合は、ブラウザはハンドシェイクエラーとなり動きません
サーバプッシュはまだサポートされない模様。 これを使ってみたかったのだが、残念。
試すこと
せっかく試すので、nginx単体と静的ファイルだけでは面白く無いので、裏にrailsアプリを動かすことにした。
環境準備
OS
CentOS 6.6を使用。
Vagrantfileを適当に作成してvagrant up
を実行。
Vagrantfile
Vagrant.configure(2) do |config| config.vm.box = "http2-test" config.vm.box_url = "https://github.com/tommy-muehle/puppet-vagrant-boxes/releases/download/1.0.0/centos-6.6-x86_64.box" config.vm.network "private_network", ip: "192.168.33.80" end
パッケージのインストール
とりあえずこちらも適当にvagran ssh
してパッケージをインストール。
sudo yum install -y git readline-devel \ libxml2-devel libxslt-devel \ mysql mysql-server mysql-devel \ nodejs npm ImageMagick ImageMagick-devel
rubyのインストール
いつものrbenvのお決まり作業。 githubのgistを使って2.2.3をインストール。
curl -L http://git.io/vWmCs | sh
短縮URLを使ったが、ソースは以下。
nginx 1.9.5のインストール
sudo rpm -i http://nginx.org/packages/mainline/centos/6/x86_64/RPMS/nginx-1.9.5-1.el6.ngx.x86_64.rpm
自己証明書作成
毎度の事なのでコマンドだけ。
openssl genrsa -des3 -out server.key 2048 openssl req -new -key server.key -out server.csr cp server.key server.key.org openssl rsa -in server.key.org -out server.key openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt # nginxのディレクトリにコピーしておく sudo cp -ip server.crt server.key /etc/nginx/
nginxのログの設定
HTTP/2で動いているか確認するためにログ出力変更。 GETのところでもわかるが、最後の「h2:$http2」を追加。
/etc/nginx/nginx.conf
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" ' 'h2:$http2';
こうすることでHTTP/2の場合は「h2:h2」と出力される。 HTTP/2でない場合は「h2:」となる。
実際のログ
192.168.33.1 - - [21/Oct/2015:04:03:10 +0200] "GET / HTTP/2.0" 404 640 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36" "-" h2:h2
railsアプリ作成
railsプロジェクト作成。
# nokogiriインストール用 bundle config build.nokogiri --use-system-libraries # railsのインストール gem install rails # プロジェクトディレクトリ作成 mkdir projects cd projects # プロジェクト作成 rails new http2 -d mysql -T --skip-bundle
Gemfileに必要なgemを追加。
Gemfile
# rails 4.2.4とmysql2の0.4.0の相性が良くないのでダウングレード gem 'mysql2', '~> 0.3.20' # 以下を追加 gem 'therubyracer' gem 'unicorn'
bundle installしてwelcomeコントローラを作成。
# bundle install bundle --path vendor/bundle # welcomeコントローラ作成 ./bin/rails g controller welcome
app/controllers/welcome_controller.rb
class WelcomeController < ApplicationController def index end def nginx end # サーバプッシュテスト用(今回はnginxがまだ対応していないので関係ない) def push response.headers['Link'] = '</assets/14.jpg>; rel=preload' render :index end end
まずはrails側で画像ファイルを裁く場合のテスト。
app/views/welcome/index.html.erb
<!DOCTYPE html> <html> <head></head> <body> <h1>hello http/2!</h1> <img src="/assets/14.jpg"> <img src="/assets/150704-05.jpg"> <img src="/assets/20150707100135.jpg"> <img src="/assets/B-U2e0ACcAAAuq6.jpg"> <img src="/assets/akagami.jpg"> <img src="/assets/yasashi.jpg"> </body> </html>
静的ファイルはnginxで裁く場合のテスト。 nginxの設定で「/img/」はunicornではなくnginxが裁くように設定している。
app/views/welcome/nginx.html.erb
<!DOCTYPE html> <html> <head></head> <body> <h1>hello http/2!</h1> <img src="/img/14.jpg"> <img src="/img/150704-05.jpg"> <img src="/img/20150707100135.jpg"> <img src="/img/B-U2e0ACcAAAuq6.jpg"> <img src="/img/akagami.jpg"> <img src="/img/yasashi.jpg"> </body> </html>
ルーティングを設定。
config/routes.rb
root 'welcome#index' get 'nginx' => 'welcome#nginx' get 'push' => 'welcome#push'
unicornの設定、起動
unicornタスクを作成。
./bin/rails g task unicorn
いつものごとく設定。 環境(RACK_ENV)は環境変数を利用するようにしてある。
lib/tasks/unicorn.rake
namespace :unicorn do ## # Tasks ## desc "Start unicorn for development env." task(:start) { config = Rails.root.join('config', 'unicorn.rb') sh "bundle exec unicorn_rails -c #{config} -D" } desc "Stop unicorn" task(:stop) { unicorn_signal :QUIT } desc "Restart unicorn with USR2" task(:restart) { unicorn_signal :USR2 } desc "Increment number of worker processes" task(:increment) { unicorn_signal :TTIN } desc "Decrement number of worker processes" task(:decrement) { unicorn_signal :TTOU } desc "Unicorn pstree (depends on pstree command)" task(:pstree) do sh "pstree '#{unicorn_pid}'" end def unicorn_signal signal Process.kill signal, unicorn_pid end def unicorn_pid begin File.read("/tmp/unicorn.pid").to_i rescue Errno::ENOENT raise "Unicorn doesn't seem to be running" end end end
unicornの起動。
./bin/rake unicorn:start
nginxとunicornの連携
HTTP/2テスト用に以下のnginx設定ファイルを追加。
/etc/nginx/conf.d/http2.conf
upstream unicorn { server unix:/tmp/unicorn.sock fail_timeout=0; } server { listen 443 ssl http2; ssl_prefer_server_ciphers on; ssl_ciphers AESGCM:HIGH:!aNULL:!MD5; server_name localhost; ssl_certificate /etc/nginx/server.crt; ssl_certificate_key /etc/nginx/server.key; # /img/は静的に対処する location /img/ { root /var/www/html; } try_files $uri/index.html $uri @unicorn; location @unicorn { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://unicorn; } }
HTTP/2がオフの状態(HTTP)を試すためにdefault.confを修正。
/etc/nginx/conf.d/default.conf
erver { listen 80; server_name localhost; error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } try_files $uri @unicorn; location @unicorn { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://unicorn; } }
nginxの起動。
sudo service nginx start
実際に試してみる
後はブラウザ(Chrome)からアクセスして、Networkを見てみる。
HTTP
見事に同時に6コネクションでブロックして終わってから残りをアクセスする感じになっている。 結果的には一番遅い。
HTTP/2(すべてrails)
静的ファイルのリクエストがすべて同時に開始されている。 HTTPでの処理より早い。
HTTP/2(画像はnginx)
同じく静的ファイルのリクエストがすべて同時に開始されている。 今回の結果としてはrailsを通すより早くなっている。 (想定通りの結果)
リクエスト、レスポンスヘッダについて
HTTP/2はヘッダ周りの仕様も変わっているので確認すると以下のようになっていた。
HTTP
HTTPはいつもどおりヘッダは大文字から始まっている。
リクエスト
レスポンス
HTTP/2
ヘッダ名は小文字になっていて一部のヘッダは「:method:」とかになっている。 これはStatic Tableに定義されている物だ。 よく使うものは番号を振っておいて、データ転送量を減らす目論見。 この辺の考えはmod_ajpでも取り入れられていた。
リクエスト
レスポンス
まとめ
nginxや他のサーバもHTTP/2を実装してきている。 どこまで既存のアプリがそこまで対処なしで移行出来るかが今後のポイントとなる。 ブラウザにキャッシュされているリソースをサーバプッシュで送るのは無駄なので サーバプッシュ周りが実装されたら、nginxやアプリ側でどう対応していくのかのベストプラクティスが必要。 h2oのようにCookieでブラウザのキャッシュのやり取りをして最適化するという手法もあるようだ。 Ruby界隈ではRackの対応やRailsでassets周りがHTTP/2に殆ど意識せずに移行出来るなら、HTTP/2は広まっていくだろう。 この辺の腰が重いと「HTTP/1.1で良くない?」という流れにもなる。
サーバプッシュ周りで素晴らしい使い方が発見され、HTTP/2が流行ることを期待したい。