X-Accel-RedirectでContent-Dispositionが上書きできる件

認証付きでCDNやクラウドストレージに置いたファイルをダウンロードさせたいことありますよね。一般的にはアプリケーションから署名付き(Pre-signed)なURLを発行するやり方を採用することが多いんじゃないかなーと思いますが、アプリケーションサーバーから直接ダウンロードせざるを得ないこともたまにあります。

  • XHRでダウンロードしたいがCDNとオリジンが異なり、CORSも使えない場合
  • セキュリティ的に署名付きURLが要件と合わない場合

そんなときは、愚直にアプリケーションからファイルをダウンロードさせるよりも、アプリケーションは認証だけやって、Nginxにコンテンツ配信の処理を任せたほうが、アプリケーションの負担が少なくて済みます。具体的には、認証後にアプリケーションがX-Accel-Redirectレスポンスヘッダーにファイルの場所を教えてやれば、あとはNginxがよしなにやってくれます。

def accel_redirect(blob) response.headers['Content-Type'] = blob.content_type response.headers['Content-Disposition'] = ActionDispatch::Http::ContentDisposition.format( disposition: 'attachment', filename: blob.filename ) # X-Accel-Redirect header response.headers['signed-url'] = blob.signed_url response.headers['X-Accel-Redirect'] = '/files' head :ok end

location = /files { internal; # If you use variables in proxy pass you need to # tell nginx how to resolve your host # otherwise you will get 502 errors # you could also use google 8.8.8.8 resolver 10.0.0.2; # Do not touch local disks when proxying # content to clients proxy_method GET; proxy_pass_request_body off; proxy_max_temp_file_size 0; set $signed_url $upstream_http_signed_url; proxy_ssl_server_name on; proxy_pass $signed_url; }

Nginx configuration

試してみると、signed-urlヘッダーに指定したURLのファイルが正常にダウンロードされました。ファイル名もContent-Dispositionヘッダーに指定したとおりです。しかしここでちょっと気になることがあります。アプリケーションから返したsigned-headerとX-Accel-Redirectヘッダーはクライアントに返されるレスポンスには含まれていませんが、Content-TypeとContent-Dispositionは指定した値がレスポンスに含まれています。これは期待したとおりでまったく問題はないのですが、なぜそうなるのだろうと思って調べたら、公式サイトに答えが書いてありました。

Note that the following HTTP headers aren’t modified by NGINX:

これらのHTTPヘッダーはNGINXによって変更されません☺️

Content-Type Content-Disposition Accept-Ranges Set-Cookie Cache-Control Expires

というわけで、X-Accel-Redirectヘッダーを指定したとき、Content-TypeやContent-Dispositionを含む上記のヘッダーについては、アプリケーションからセットした値がクライアントに返される、それ以外はプロクシしたリクエストのレスポンスヘッダーがそのまま返されるよ、という話でした。