弱虫のためのRFCまたは1つの調査のストーリー

前文 :ある日、ユーザーのページの読み込みを高速化するために、CDNをサイトに接続することにしました。 いくつかの検索の後、選択はHighwindsに落ちました。 彼らは、必要なすべての機能をサポートし、彼らと非常においしい価格を交渉することに成功したと述べました。 Highwindsを介して動作するようにサイトを正常に移動した後、モバイルアプリケーション用のREST APIをそれらに切り替えてみませんか。 そして、楽しみが始まりました。



テストデバイスのAPIをCDN経由で動作するように切り替えました。iOSが動作し、Androidも動作しているようですが、しばらくお待ちください。 Androidアプリケーションでは、GETおよびHEADリクエストのみが機能し、POST、PUTなどは502に該当します。iOSおよびAndroidアプリケーションのトラフィックの簡単な調査と比較の後、Androidはリクエストで「Transfer-Encoding:chunked」ヘッダーを送信します。



カールを使用してAPIページをプルしようとします。



curl https://cdn.api.example.com -XPOST -d 'test=data'
      
      





動作します。 そして、あなたがこのようにしようとしたら:



 curl https://cdn.api.example.com -XPOST -d 'test=data' -H 'Transfer-Encoding: chunked'
      
      





ええ、CDNを使用しなくてもこのようなクエリは問題なく機能しますが、機能しません。

nginxのアクセスログでは、リクエストが400 Badリクエストコードで失敗したことがわかります。



ただし、curlは「Transfer-Encoding:chunked」ヘッダーを送信するが、データを適切に生成しないという問題がある可能性があります。 チャンクでデータを送信する小さなPythonスクリプトを作成して、このオプションを確認しましょう。



 import requests import logging import httplib as http_client http_client.HTTPConnection.debuglevel = 1 logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) requests_log = logging.getLogger("requests.packages.urllib3") requests_log.setLevel(logging.DEBUG) requests_log.propagate = True def test(): yield 'data' yield 'test' s = requests.Session() data = s.post('https://cdn.api.example.com', data=test())
      
      





スクリプトは30秒間ハングし(30秒はCDN設定の書き込み要求タイムアウトです)、エラーで失敗します。



出力には以下が表示されます。



 send: 'POST cdn.api.example.com HTTP/1.1\r\nHost: cdn.api.example.com\r\nConnection: keep-alive\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nUser-Agent: python-requests/2.18.4\r\nTransfer-Encoding: chunked\r\n\r\n' send: '4' send: '\r\n' send: 'data' send: '\r\n' send: '4' send: '\r\n' send: 'test' send: '\r\n' send: '0\r\n\r\n' reply: 'HTTP/1.1 502 Bad Gateway\r\n' header: Date: Mon, 11 Dec 2017 22:05:04 GMT header: Connection: Keep-Alive header: Accept-Ranges: bytes header: Cache-Control: max-age=10 header: Content-Length: 0 header: X-HW: 1
      
      





要求が正しいことがわかります。最後のチャンクの後に、長さがゼロのメッセージ「0 \ r \ n \ r \ n」があり、すべてのチャンクが送信されたことをWebサーバーに伝えています。 ただし、CDNサーバーはさらに多くのチャンクを待機し続け、30秒後にタイムアウトにより低下します。



しかし、CDNのすべての非難を非難するには時期尚早です。 思い出すと、リクエストはnginxに届きますが、コード400で落ちます。nginxが原因である可能性はありますか? これを確認するには、トラフィックをダンプし、Wiresharkの[Follow TCP Stream]オプションを選択して、読み取り可能な形式のデータを表示します。



 POST / HTTP/1.1 Date: Tue, 12 Dec 2017 07:19:48 GMT Host: cdn.api.example.com Connection: Keep-Alive Accept-Encoding: gzip, deflate Accept: */* User-Agent: python-requests/2.18.4 Transfer-Encoding: chunked
      
      





ご覧のように、nginxはヘッダーを受信しましたが、POSTデータはヘッダーに到達しませんでした。CDNサーバーがクライアント502を提供してnginxから切断すると、無効な要求を受信したことを示すメッセージをログに書き込む以外に何も残っていません。



最後の可能性を考えてみましょう。CDNは「Transfer-Encoding:chunked」で動作する必要はないかもしれません。アプリケーションで使用したのは私たちのせいですか? RFC 7230がそれについて考えていることを読みます 。 私たちが探しているのは、セクション3.3.1および4.1で見つかりました。 標準では、要求と応答の両方で「Transfer-Encoding:chunked」の使用が許可されています。 これは、これがHTTP / 1.1の必須部分であり、この標準を実装するすべてのアプリケーションでサポートされる必要があることを個別に示しています。



問題がCDN側のHTTPサーバーの誤動作であるというすべての証拠を収集しました。 サポートのチケットを作成し、問題の詳細をすべて明確にし、エンジニアとコミュニケーションをとった後、すばらしい回答を得ました。

これはシステムのバグではなく、リクエスト内のチャンクエンコーディングが設計上機能していないことを確認しています。

この後、追加することすらありません。 また、HighwindsがVarnishやNginxなどのHTTPサーバーのオープンソース実装を使用し、「設計上」そのような機能を独自に作成しなかった場合、問題は発生しなかったことにも注意してください。 HTTPプロトコルの偽物に注意してください。



All Articles