X-Accel-Redirect基本設定:OpenBSD/nginxによる

#PCとプログラム覚え

概要

はじめに

認証付きで画像・ファイルにアクセス設定をするとなると、当然、そのファイルに直接アクセスされるような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;
}

などとしておきます。

Popup