かまたま日記3

プログラミングメイン、たまに日常

HerokuのHTTP Routingの仕組みを学ぶ

HTTP Routing | Heroku Dev Center

職場でHerokuをプロダクション環境で使ってるので一通り目を通してみました。

以下は2015年10月12日ごろの上記ページの内容のオレオレ翻訳メモです。
内容の正確性は全く保証しませんw (訳も〜ですます調と〜だ調が混在してますし)
もし正しくない箇所があったらご指摘いただければと思います。


Cedarスタック上のすべてのweb dynoに対するHTTPリクエストのエントリーポイントはheroku.comである。

Routing

  • heroku routerの役割はweb dynoの場所を特定してその中の一つにリクエストを転送すること
    • HTTP 1.1対応、HTTP 1.0も互換性も維持している

Request distribution

  • routerは無作為アルゴリズムでweb dyno間のHTTPリクエストを振り分けしている

Request queueing

  • routerごとに各アプリに対するアクティブリクエストカウンターを保持しており、dynoあたり50n(n=web dynoの数)を上限としている。
    • 上限を超えるとルーターはH11のレスポンスを返す

Dyno connection behavior

  • web dynoに対するコネクション確立が拒否もしくは5秒以内に終わらない場合は、routerはそのdynoを隔離し5秒間別のコネクションを確立させようとしない。
    • 別のrouterは確立させに行くかもしれない(それぞれ独立しているので)
  • コネクション拒否もしくはタイムアウトした場合最大10回(web dynoの数が10以下ならweb dynoの数)再施行する。それでもダメならH19やH21のエラーを返す
  • 全部のdynoが隔離された場合、75秒後(この期間は増加していく)にリトライし、ダメだったらH99をエラーを返す

Timeouts

  • HTTPコネクション確立後、30秒以内に最初のウィンドウ(TCPウィンドウのこと?)が返却されないとH12エラーをログに吐く
  • 最初のレスポンスを受け取ったあとは55秒以内にウインドウを返却しないとH15やH28エラーをログに吐き、コネクションが切断される

Simultaneous connections

  • herokuapp.com のルーティングスタックはweb dynoに多くのコネクションを確立するのを許可している
  • なのでwebserverは応答性を最大化するため複数コネクションに対応したものを選ぶべき

Request buffering

  • routerは1MBのレスポンスバッファを維持している
  • これは1MB以下のレスポンスであればクライアントの受信レートによらず返すことが可能であることを意味する(翻訳あやしい)
  • 1MBを超えるレスポンスをの転送レートはクライアントの受信がどれだけ早いかによって制限される

Heroku Headers

Heroku router log format

Caching

HTTP Cacheing参照

WebScoekts

サポートしてる

Gzipped responses

  • Cedarアプリへのリクエストは(nginxのようなHTTPプロキシサーバを通している訳ではなく)直接アプリケーションサーバに送られるため、レスポンス圧縮はアプリケーション側で行う必要がある。

Supported HTTP Methods

  • すべてのHTTP methodをサポートしている、RFCで定義されてないCONNECTメソッドもサポートしている。

Expect: 100-continue

参考: HTTP 100-continueとは

  • http-end-to-end-continueをonにすることでExpect: 100-continueヘッダーをサーバに通し、100 continueレスポンスを透過的に扱ってくれる
$ heroku labs:enable http-end-to-end-continue

Corner cases

  • RFC 2068では100 Continueというのレスポンス仕様が定義されている、それ以降のRFCExpect: 100-continueが含まれており100 continueもそのメカニズムに含まれている
    • なので後方互換性を考えたらExpectヘッダがなくても極力100 Continueを返した方が良いが、そうしないことを推奨している
  • クライアントはサーバが即座にbodyデータを受け取って処理した場合、100 Continueを返さない場合があることを認識しておく
  • Expectヘッダーは複数の値を保つ場合があるので、パースをするときは気をつける
  • コネクションを管理する複数のヘッダによる予期しない相互作用がある
    • websocketのConnection: UpgradeExpect: 100-continueが同時に来たらどうする?
    • 100 ContirnueConnection: closeが同時に来たらどうする?受信時に切断する?

上記のcorner caseに対してHeroku routerは一貫性をもった振る舞いを提供しなければならない。

Proxy requirements

Expectヘッダーは本来はclientとserver間のE2Eメカニズムだけど間のproxyの協調も必要になるのでRFCで定義されてる

  • ヘッダーは現在のままサーバに渡す
  • プロキシが対象サーバがHTTP 1.0以下のバージョンであることを知っている場合、417 Expectation Failedを返す
  • HTTP 1.0以下のClientがExpect: 100-Continueをつけずにリクエストを送り、サーバがそれでも100 Continueレスポンスを返した場合、プロキシはそのレスポンスを取り去り最終的なステータスが来るのを待つ

Heroku router 100-continue support

  • HTTP 1.0(より古い)クライアントの100 ContinueレスポンスはExpect: 100-continueがリクエストに入ってない場合、剥ぎ取られる(「返却されない」の意?)。
  • routerはリクエストボディを送るとき、(サーバに?)100 Continueレスポンスを要求しない、が、(ボディを送るまでの?)待ち時間はクライアントに委ねる
  • Expectヘッダーに100 Continue(大文字小文字区別なし)以外の値が含まれていた場合routerは自動的に417 Expectation Failedレスポンスを返してdynoとのコネクションをクローズする
  • WebSocket upgradeが要求されたとき、(routerは?)dynoにありのままを送信し、どんなレスポンスが来ても引き受ける。100 ContinueステータスがWebSocket upgradeを無視して何かしらのコードを返すかもしれない。また101 Switching ProtocolExpectヘッダーの振る舞いを無視する。HTTPbis Draft(HTTP1.1の仕様?)を尊重するための注意、100 Continueがサーバから受け取ったあと、routerはそれでも101 Switching Protocolを待ち受けます。
  • routerは100 Continueの時のConnection: closeを無視して最終的なレスポンスが届いた時のcloseだけを期待します。RFCの仕様として、コネクションは"現在のrequest/responseが完了した後"closeされるべきで、100は終端ステータスではありません。ただし注意、Connection: closeはhop-by-hopな仕組みなので、routerは必ずしもclientへのconnectionをcloseする必要はなく、またcloseを転送しないかもしれない。
  • routeは100 Continueレスポンスからのすべてのヘッダーを引き剥がします(「転送しない」の意?)、それはRFCで規定されており実装をよりシンプルにします。
  • routeは5xxのエラーコードをサーバが最初の100 Continueの後で100 Continueを返した場合に返します。routerは無限の1xxストリームにまだ対応していません。
  • routerは100 Continueが先行しているかいないかに関係なく終端ステータスが来た後にサーバとのコネクションをcloseします。dynoへのコネクションはkeep-aliveしません。
  • routerは100 Continueが先行していない終端ステータスが来た後にクライアントへのコネクションをクローズします。これは次のリクエストを処理することができるサーバを保持する前に、とにかくリクエストボディを送信する必要があるクライアントを持つことを避けます。(意味分かってない、サーバ側が100 Continueのリクエストボディを受ける前に次のリクエストを処理しないようにするということ?)

他のメカニズムもそのプロトコルをそのまま尊重するべきであり、routerはRFCによる仕様としてのリクエストを転送するべきである。

Http versions supported

Heroku routerはHTTP/1.0とHTTP/1.1だけをサポートしており、HTTP/0.9以下はもうサポートしていません。SPDYとHTTP/2.0は現時点ではサポートしていない。

routerの振る舞いはHTTP/1.1の仕様に出来るだけ従っているが、特例はHTTP/1.0のためになされなければならない。

  • routerはたとえクライアントがHTTP/1.0が使っていようがいまいが、自身はHTTP/1.1を使うと示す。
  • routerは細切れのレスポンスから通常のHTTPレスポンスへの必要な変換を取り扱う。(ギガバイト級のデータを扱うことを除いて)そうするために、クライアントへのレスポンスはコネクションの終了によって境界づけられる。(参照 (Point 4.4.5](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4))
  • routerはクライアントがリクエストごとにコネクションをクローズしたいと決めつける(keep-aliveはない)
  • HTTP/1.0クライアントは明示的にconnection:keep-aliveヘッダを送ってくるかもしれない。keep-aliveメカニズムは1.0にバックポートされていないにもかかわらず、routerは、要求する振る舞いが現時点でのHTTP/1.1の振る舞いに似ていると仮定します。

HTTP validation and restrictions

Request validation:

  • chunked encodingとcontent-lengthが両方存在する場合、chunked encodingを行う
  • 複数のcontent-lengthヘッダーが存在し、同じ値の場合は一つにマージする
  • content-lengthが複数の値を持つ、もしくは複数のcontent-lengthヘッダが存在する場合、400statusを返す
  • ヘッダーは8192byte/行に制限されている(ヘッダー名は1000byteまで)
  • Hop-by-hop ヘッダは混乱を避けるために剥ぎ取られる
  • 最大1000ヘッダまで
  • HTTPリクエストのRequest Line(リクエスト行?)は8192byteまで
  • Request Lineはスペースで動詞(メソッド?),パス, HTTPバージョンを分割していることが期待されている

Response Validation:

  • Hop-by-hop ヘッダは混乱を避けるために剥ぎ取られる
  • ヘッダは512kb/行に制限されている
  • Cookiesははっきりと8192byteに制限されている。これは大きなcookieをほとんど受け入れないような共通の制限(たとえばCDNが課すような)から保護するため。そういうケースでは開発者はふとしたことから大きなcookieをセットする、そしてそれはユーザーに返される、そしてその時になってユーザーはリクエストが拒否されたことを知る。
  • ステータス行(HTTP/1.1 200 OK)は8192byteの長さに制限される

これらの制限を破ったアプリケーションは502 Bad Gatewayのレスポンスを伴って失敗となる、そしてH25 errorがログストリームに記述される。リクエスト制限を破ったクライアントは400 Bad Requestレスポンスを伴って失敗となる。

加えて、HTTP/1.1のリクエストとレスポンスがデフォルトでkeep-aliveを期待されているうちは、初期リクエストがはっきりとルーターからdynoに対してのconnection: closeヘッダを持っていたら、dynoはcontent-encodingを指定していない、もしくははっきりとcontent-lengthを指定していないようなコネクション切断によって境界づけられたレスポンスを送ることが出来る。

Protocol upgrades

以前のHeroku RouterはHTTPプロトコルアップグレードをWebSocketのみに制限していた一方で、新しいrouterはすべてのアップグレートに寛大である。

特徴は実装に関係している

  • どんなHTTP動詞(メソッド?)もアップグレード可能なコネクションを伴って使うことが出来る。
  • HTTP HEADはたいてい、すべての行(たとえばcontent-lengthに関して)を送るようなちゃんとしたレスポンスを要求しないにもかかわらず、HEAD リクエストは101 Swithcing Protocolsを伴うレスポンスと連携するのに適している。アップグレードを求めないdynoは異なったステータスコードを送るべき、そしてコネクションはアップグレードしないだろう。

Not supported

  • SPDY
  • HTTP/2.x
  • 100-continue以外のコンテンツを伴ったExpectヘッダ(417が発生)
    • 上の方の100-continueの項を参照
  • WEBDAVのようなHTTP拡張
  • content-lengthやchunked encodingを伴ったプロキシされてないHEAD,1xx, 204, 304レスポンスは決してこないBodyを中継しようとする。
  • CRLF(\r\n)以外のヘッダ
  • HTTPコンテンツのキャッシュ
  • dyno上で動いているサーバのHTTPバージョンのキャッシュ
  • 長時間事前割当されたアイドルコネクション。その上限はアイドルコネクションがクローズされる1分前に設定されます。
  • Hostヘッダを伴わないHTTP/1.0リクエスト、それはfull URLがリクエスト行に送信されている時でさえも対応しません。