経緯
- レスポンスをすべて組み立てるのに時間がかかる処理があるので、処理全体が完了する前にレスポンスを返したい。
- HTTP 1.1 では HTTPヘッダで
Transfer-Encoding: chunked
を設定することで、body を任意の長さの chunk (かたまり) に分割してクライアントに返すことができることになっている - Rails には ActionController::Live というモジュールが用意されている
- そのモジュールを include して、
response.stream
に文字列を出力することで、レスポンスを任意の単位で送出することができる - 公式のドキュメント等には、その使用例として Server-Sent Events (SSE) が挙げられているが、別に SSE を利用しなくてもよい
- そのモジュールを include して、
- HTTP 1.1 では HTTPヘッダで
問題
ネタ元:
Rack::ETag
ミドルウェアでバッファリングされているらしく、本来、逐次レスポンスが返ってくるところ、response.stream.close
するまでレスポンスが返ってこない挙動になってしまっています。
回避方法
rack を 2.1.x 系にデグレードするか、あるいは、以下のようにコントローラで HTTPヘッダを明示的に指定することで Rack::ETag
によるバッファリングを回避できるようです。
headers['Last-Modified'] = '0'
headers['ETag'] = '0'
Last-Modified
とETag
は、 HTTP条件付きリクエスト で使用され、コンテンツの中身が前回取得のものと同じであればリクエストを飛ばさずにキャッシュを利用するようなケースでよく利用されます。
しかし、(個人的な印象ですが)APIのようなコンテンツが動的に生成されるような場合においては、キャッシュが利用できるケースは限られていて、キャッシュを利用したくないケースが多いと思います。
今回のケースでも、APIで動的コンテンツを返すような箇所だったので上記のような回避策でも問題無いと判断しました。
こんなので一日が潰れてしまった...
参考リンク
- Rails 5.2: ActionController::Live does not stream responses, instead returns entire response at once · Issue #38780 · rails/rails
- Version 2.2.x breaks Rails Actioncontroller:Live streaming · Issue #1619 · rack/rack
- ActionController::Live
- ActionController::Live | TECHSCORE(テックスコア)
- WEBRick で HTTP streaming (Chunked transfer encoding) - Qiita
- HTTP条件付きリクエスト