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

最終更新: