X-Accel-Redirect基本設定:OpenBSD/nginxによる
概要
はじめに
認証付きで画像・ファイルにアクセス設定をするとなると、当然、そのファイルに直接アクセスされるようなURIは使えません。
その場合、認証下にある画像出力プログラムで、対象ファイルを絶対パスで読み込み そのデータをヘッダ付きで出力する、というのがよくあるパターンです。
各種言語にはデータ読み込み出力関数があるので、たとえば、PHPの場合なら、
//認証下+jpeg表示
$path= 対象フルパス
$path = rawurldecode($path); // RFC3987を無視したRFC3986(テック利害)へのブラウザ対策
while (ob_get_level()) {
ob_end_clean();
}
header('Content-Type: image/jpeg');
header('Content-Length: ' . filesize($path));
readfile($path);
exit;
みたいな感じです。
でもこれは、数件の軽いファイルならいいのですが、 100とか200とかの画像〔サムネ)をタイルで並べて、 fancyboxで拡大するとかやると、やっぱり体感的にも遅いんですよね。
対応としてはlazyつかったり画像サイズを最適化するという手段もありますが、 小手先の知恵にすぎず、まずはサーバサイドで直に画像データを出すという先祖返りができれば一番いいわけです。
というわけで、X-Accel-Redirectの出番となります。
X-Accel-Redirect
歴史背景の手短な記述
2000年代半ばに、アプリケーションサーバの配布負荷を軽減するために、アプリケーションをデータ配信中間点にすることなく、 各種HTTPDが、内部処理で直接ファイル配信する仕組みがでてきます。認証はアプリで配信はサーバが担うという機能分離です。
Lighthpdが先べんをつけ(X-SENDFILE)、Apacheがxsendfileモジュール実装、NginxのX-Accel-Recirectを実装します。だいたい2004-6年のことです。
そして、2010年代にNginxのプレゼンスがあがるにつれ、X-Accel-Redirectが一種の標準になって、いまに至るという次第です。
このよく使われている技術について、ここで、あえてメモを書くのは、以前に OpenBSDのchrootはガチである で述べたように、OpenBSD/Nginxの挙動が<セキュリティ厳密型>なので、このルートが動作するかどうかということを確認するためでした。まあ、動作するからここで書いてるわけですけど。
基本的な動作プロセス
まず、Nginx側には、
internal
ディレクティブが必要です。 この<キーワード>は、 locationブロック(ディレクティブ)の中でのみ、機能します。
location /_img/ {
internal;
root /var/www/protected;
}
あるいはaliasで、
location /_img/ {
internal;
alias /var/www/protected/;
}
でもいいです〔末尾スラッシュをつけることに注意)。
どちらも、「指定された内部URI」を「サーバー上の物理パス」に変換する機能を果たします。
違いは、rootのばあいは、/var/www/protected/_img/が、パスになり、aliasの場合は、/var/www/protected/となるというだけです。
通常はaliasのほうを使うことが多いです(直感的にパスもわかりやすい)
一方、アプリケーション側では、ヘッダを一つ書きます
header('X-Accel-Redirect: /_img/foo-bar.pdf');
すると、aliasの場合、物理フルパスは
/var/www/protected/foo-bar.pdf
で、そのfoo-bar.pdfをNginxは出力処理します。
ようするに、X-Accel-Redirectのキーワードをつかって、 Nginxがフルパスを取得し内部処理したうえで、出力するという構図です。
実際のアプリケーション側では /var/www/protected/any/abc.pdfの、any/abc.pdfなどを$f_path変数にいれて、 次のように処理すると、動的変更が可能、ということになります。
header('X-Accel-Redirect: /_img/'.$f_path);
OpenBSD/Nginxで、シンボリックリンクをつかって動作可能か
nginxで、chrootを外すには起動時に-uオプションをつけます。
rcctl set nginx flags "-u"
して、
rcctl get nginx flags
-u
こうなってれば、/var/wwwというOpenBSDのnginx chroot外のパスを利用できます。
というのは、ちょっと楽観的すぎるので</var/wwwへのシンボリックリンクは効きます>と言っておきます(この微妙な表現理由は、 OpenBSDのchrootはガチであるをご覧ください)。
そのうえで、たとえば、実体が、/home/www/protectedにあるものを
ln -s /home/www/protected /var/www/protected
して、ファイル認識できるか、ということです。
はい、できます。〔結論は簡単w)。
補足:実際のアプリケーション側記述
<?php
/*X-Accel-Redirect*/
if (session_status() === PHP_SESSION_ACTIVE) {
session_write_close();
}
while (ob_get_level()) {
ob_end_clean();
}
$rel = ltrim(rawurldecode($image_folder_name, '/')// /var/www/protected以下のパス
. '/'
. ltrim(rawurldecode($img_file), '/');//ファイル名
header('Content-Type: image/jpeg');
header('X-Accel-Redirect: /_img/' . $rel);
exit;
補足2:nginxのinternalチューニング
速度改善のために、チューニング要素を掲出しておきます。
| 指令 | 既定 |
|---|---|
sendfile |
off |
tcp_nopush |
off |
tcp_nodelay |
on |
open_file_cache |
off |
open_file_cache_valid |
60s |
open_file_cache_min_uses |
1 |
open_file_cache_errors |
off |
こうなってますので、
location /_img/ {
internal;
alias /var/www/protected/;
sendfile on;
tcp_nopush on;
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
}
などとしておきます。