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を含む上記のヘッダーについては、アプリケーションからセットした値がクライアントに返される、それ以外はプロクシしたリクエストのレスポンスヘッダーがそのまま返されるよ、という話でした。