概要
この記事の内容を大まかにまとめると以下になります。
ActionController::Liveを使っていてstream.close
されたら、cookieの値を変更するという処理を実装しようとしたのですが、cookieの値が変更できないという問題に直面しました。
そもそも、今回はチャンク形式転送エンコーディングという方式でレスポンスを返していたのですが、その仕組みは最初にクライアントにレスポンスを送って、そのレスポンスボディに逐次データを書き込んでいるというものでした。
そのため、レスポンスを最初に送っているため、レスポンスヘッダーが変更不可能な状態になります。よって、データを送りはじめたら、それ以降同一レスポンス内ではcookieは変更できないということでした。
環境
- Rails 6.0.3
本題
実装しようとしていたこと
フロントエンドがVue.js、バックエンドがRailsと別れている中で、ファイルダウンロードの仕組みに追加実装しようとしていました。
そのファイルダウンロードの仕組みは、ActionController::Liveを使用して、ある程度のデータの塊に分割してクライアントにデータを送るというものでした。(具体的には以下のようなコードになります)
class FileDownloadsController < ApplicationController include ActionController::Live def create file = fetch_file # fetch_fileはファイルをとってくる処理 # 100KBごとにクライアントにデータを送る while (chunk = file.read(100000).presence) response.stream.write chunk end response.stream.close end end
上記のコードがある中で、今回私が実装しようとしたものは、ダウンロードの完了を検知して画面の表示を変えるというものでした。
当初考えた実装方針は以下のようなものになります。
フロントエンド側は、ファイルダウンロードボタンを押された後から、1秒に1回ほどの頻度でcookieを監視する
FileDownloadsController#create
で、ダウンロードが完了したら、cookieにdownloading=0
などの値を入れるフロントエンドは、
downloading=0
になったことを検知できたら画面の表示を変更させて、cookieの監視をやめる
コードレベルでは以下のようなイメージです。
class FileDownloadsController < ApplicationController include ActionController::Live def create cookie[:downloading] = 1 # ここを追加 file = fetch_file # fetch_fileはファイルをとってくる処理 # 100KBごとにクライアントにデータを送る while (chunk = file.read(100000).presence) response.stream.write chunk end response.stream.close cookie[:downloading] = 0 # ここを追加 end end
ハマったこと
上記のようなコードを実装して、フロントエンド側でcookieを監視してdownloading = 1
となっていることは確認できました。
しかし、ダウンロードを完了してもdownloading = 0
には変更されていないという問題がありました。
なぜ上記の方法で実現できなかったのか
そもそも、どうやって分割してデータが送られているのかという大枠を知らなければなりませんでした。
具体的にはチャンク形式転送エンコーディングという方法がHTTPレイヤーでは使われています。
Transfer-Encoding - HTTP | MDN に記載がありますが、チャンク化転送エンコーディングというのは、データを分割してそれを逐次送っていくという仕組みです。
具体的には、最初にレスポンスヘッダーとレスポンスボディの最初のチャンクを送って、それ以降は\r\n
区切りで分割したデータがレスポンスボディに逐次書き込まれていきます。
このような仕組みにより、ファイルの分割ダウンロードが実現されていました。
ここで、最初にレスポンスヘッダーをクライアントに送っているという都合上、レスポンスヘッダーは後から変更不可能な状態になります。
ゆえに、ダウンロードが完了した瞬間にcookieを変更することができないということでした。
感想
なぜcookieが書き換えられないのだと数時間悩んでいたのですが、HTTPレイヤーではどのような仕組みが採用されているのかに関心を向けられれば、原因が分かるのが早かったと思いました。
「ActionController::Live cookie」などで検索しても、中々情報がヒットしなかったので、同じような問題に直面した方の参考になれば嬉しいです。