本文へ移動
vast-cowのブログ
前のページへ戻る

ユーザー権限 nginx + AF_UNIX socket でローカルアプリを reverse proxy する

この記事を編集

複数のローカル Web アプリを手元で動かしていると、URL の prefix ごとに別アプリへ振り分けたいことがある。

たとえば、

http://localhost:8080/app1/

へのアクセスを、別プロセスとして起動しているアプリに reverse proxy したい。

このとき、nginx を使うと簡単にできる。ただし、通常のシステム nginx としてではなく、今回は次の方針で運用する。

方針

構成としては、nginx を localhost:8080 で待ち受けさせる。

そのうえで、たとえば /app1/ 以下を、アプリが listen している Unix domain socket に転送する。

browser
  |
  | http://localhost:8080/app1/
  v
nginx
  |
  | AF_UNIX socket
  v
/home/user/app1/sock.sock

TCP port をアプリごとに割り当ててもよいが、ローカル開発用の reverse proxy では Unix domain socket の方が見通しがよい場合がある。

設定ファイル上で、

location /app1/ {
    proxy_pass http://unix:/home/user/app1/sock.sock:/;
}

のように書けるので、/app1/ がどのアプリに対応しているかが直接分かる。

ディレクトリ構成

nginx 用のディレクトリを適当に作る。

nginx-local/
├── conf/
│   └── nginx.conf
├── logs/
└── temp/
    ├── client_body/
    ├── proxy/
    ├── fastcgi/
    ├── uwsgi/
    └── scgi/

作成例は以下。

mkdir -p ./conf
mkdir -p ./logs
mkdir -p ./temp/client_body
mkdir -p ./temp/proxy
mkdir -p ./temp/fastcgi
mkdir -p ./temp/uwsgi
mkdir -p ./temp/scgi

nginx の起動

今回の nginx は foreground で起動する。

nginx -p . -c ./conf/nginx.conf -e ./logs/error.log -g 'daemon off;'

-p . によって nginx の prefix をカレントディレクトリにする。

設定ファイル中の相対パス、たとえば ./logs/error.log や ./temp/proxy は、この prefix を基準に解決される。

-g 'daemon off;' を付けているので、nginx は daemon 化せず foreground で動く。開発用や検証用にはこの方が扱いやすい。停止するときは Ctrl-C でよい。

設定ファイル

conf/nginx.conf は以下のようにした。

worker_processes 1;

error_log ./logs/error.log;
error_log  /dev/stderr warn;
pid       ./nginx.pid;

events {
    worker_connections 1024;
}

http {
    access_log ./logs/access.log;
    access_log  /dev/stderr;

    client_body_temp_path ./temp/client_body;
    proxy_temp_path       ./temp/proxy;
    fastcgi_temp_path     ./temp/fastcgi;
    uwsgi_temp_path       ./temp/uwsgi;
    scgi_temp_path        ./temp/scgi;

    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

    server {
        listen 8080;
        server_name localhost;

        location = /app1 {
            return 301 /app1/;
        }

        location /app1/ {
            proxy_pass http://unix:/home/user/app1/sock.sock:/;

            proxy_http_version 1.1;

            proxy_set_header Host              $host;
            proxy_set_header X-Real-IP         $remote_addr;
            proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_set_header Upgrade           $http_upgrade;
            proxy_set_header Connection        $connection_upgrade;

            proxy_buffering off;
            proxy_cache off;
            proxy_request_buffering off;

            proxy_read_timeout 3600s;
            proxy_send_timeout 3600s;
        }
    }
}

ユーザー権限で起動するためのポイント

nginx をユーザー権限で起動する場合、いくつか注意点がある。

まず、listen する port は 1024 以上にする。

listen 8080;

80 や 443 のような privileged port を一般ユーザーで直接 bind しようとすると失敗する。

次に、ログ、pid、temporary file の出力先をユーザーが書き込める場所にする。

error_log ./logs/error.log;
pid       ./nginx.pid;

また、HTTP proxy 時に使われる temporary directory も明示しておく。

client_body_temp_path ./temp/client_body;
proxy_temp_path       ./temp/proxy;
fastcgi_temp_path     ./temp/fastcgi;
uwsgi_temp_path       ./temp/uwsgi;
scgi_temp_path        ./temp/scgi;

これらを明示しないと、環境によっては /var/log/nginx/ や /var/lib/nginx/ など、ユーザー権限では書けない場所を使おうとして失敗することがある。

AF_UNIX socket への reverse proxy

Unix domain socket へ proxy する場合、proxy_pass は次の形式で書ける。

proxy_pass http://unix:/path/to/sock.sock:/;

今回の例では次のようにしている。

proxy_pass http://unix:/home/user/app1/sock.sock:/;

この書き方では、/app1/ の prefix は剥がされる。

つまり、

http://localhost:8080/app1/foo

へのアクセスは、バックエンドアプリには

/foo

として渡る。

アプリ側で /app1/foo をそのまま受けたい場合は、proxy_pass の末尾の扱いを変える必要がある。

今回の用途では、nginx 側で /app1/ をルーティング用 prefix として使い、アプリ側には / 起点で渡す方針にした。

WebSocket への対応

WebSocket を通すには、HTTP/1.1 と Upgrade / Connection header の設定が必要になる。

proxy_http_version 1.1;

proxy_set_header Upgrade    $http_upgrade;
proxy_set_header Connection $connection_upgrade;

ここで $connection_upgrade は nginx の組み込み変数ではないため、http block の中で map によって定義している。

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

Connection "upgrade" を常に指定してしまうと、通常の HTTP request や SSE に対しても Connection: upgrade を付けてしまう。そこで、Upgrade header がある場合だけ upgrade にし、それ以外では close にしている。

Server-Sent Events / HTTP event stream への対応

SSE や HTTP event stream を使う場合、nginx の buffering があるとイベントが即時にクライアントへ流れないことがある。

そのため、proxy buffering を無効にする。

proxy_buffering off;
proxy_cache off;

長時間接続が切れないように、timeout も長めにしている。

proxy_read_timeout 3600s;
proxy_send_timeout 3600s;

これで、通常の HTTP、WebSocket、SSE のいずれも同じ reverse proxy 設定で扱える。

動作確認

設定ファイルの構文確認は以下。

nginx -p . -c ./conf/nginx.conf -e ./logs/error.log -t

起動は以下。

nginx -p . -c ./conf/nginx.conf -e ./logs/error.log -g 'daemon off;'

HTTP の確認。

curl -i http://localhost:8080/app1/

SSE の確認なら、curl の buffering を切る。

curl -N http://localhost:8080/app1/events

WebSocket の確認には websocat などを使う。

websocat ws://localhost:8080/app1/ws

まとめ

この構成の利点は大きく2つある。

1つ目は、nginx をユーザー権限で起動できること。

システムの nginx 設定を変更せず、root 権限も使わずに、手元の作業ディレクトリだけで reverse proxy を立てられる。ローカル開発や個人用ツールの取り回しがよい。

2つ目は、AF_UNIX socket を使うことで、設定ファイル上の対応関係が明確になること。

location /app1/ {
    proxy_pass http://unix:/home/user/app1/sock.sock:/;
}

このように書けるため、/app1/ がどのアプリに対応しているかを nginx の設定ファイルだけで把握しやすい。

ローカルで複数の Web アプリをまとめて扱う用途では、ユーザー権限 nginx と AF_UNIX socket の組み合わせはかなり扱いやすい。


この記事を編集
この記事を共有:

コメント


前の記事
WebArchive と MHTML ファイルを相互変換するシンプルなツール
次の記事
WSL の --import はいつ xz zstd にも対応したのか