\ ポイント最大11倍! /

【Webサーバー】nginx ルーティングの基礎を理解しよう!

  • リクエストを適切にルーティングする基礎が知りたい!

このような疑問にお答えします。

nginx のルーティングの流れを簡単に示すと、以下になります。

  • リクエストを受け付ける
  • 適切なサーバーを探す
  • リクエストの処理方法を決める
  • 静的ファイルを探して返す
  • プロキシ経由で処理する
  • 結果を返す

「うわ、結構な量があるな…!」と思った方も多いかもですが、これより粒度を下げてしまうと理解が難しくなってしまうので細かめに分類しました。

nginx を触るのが初めての方でもお分かりいただけるように、説明はできるだけ平易にしました。

それでは、一つ一つの処理を丁寧に説明していきます。

1. リクエストを受け付ける

ブラウザなどから送信された「HTTP リクエスト」を nginx で受け取ります。

この段階では、nginx の内部で次のことが行われます。

  • リクエストの受け入れ
  • リクエストデータの解析

少し掘り下げて解説しましょう。

リクエストの受け入れ

クライアント(Web ブラウザやアプリなど)からサーバーへの接続が成功し、データの交換が始まるプロセスです。

知識として、以下が実行されていることを覚えておくと良いです。

  • 接続の確率
  • TCPハンドシェイク
  • HTTPリクエスト受信
  • セキュリティチェック
  • リクエストのバッファリング

リクエストデータの解析

クライアントから送られてきたリクエストの解析を行います。

  • HTTPメソッドの識別
  • URIの解析
  • クエリパラメータの抽出
  • ヘッダの解析
  • ボディデータの読み取り
  • エンコーディング・文字セットの処理

ここで、リクエストの意図を判断していくことになります。

2. 接続先のサーバーを決定

nginx には通常、複数のサーバーブロック(server)が定義されます。

「定義されたサーバーブロックのうち、どのサーバーに接続すべきか?」を制御しているのが、このステップです。

nginx では、以下の流れでマッチするサーバーブロックを探しにいきます。

  1. ホスト名
  2. IPアドレス / ポート番号
  3. 上記に当てはまらない場合

詳細を確認していきましょう。

ホスト名

クライアントから送信されたリクエストには、「ドメイン名」が格納されたHostヘッダーというものがあります。

つまり、最初のステップとして「リクエストにあるドメイン名」と nginx 側の「server_nameディレクティブに定義されたホスト名」とを照合して一致を確認します。

具体例

例えば、以下のようなリクエストが送られたとします。

GET /index.html HTTP/1.1
Host: www.example.com

リクエストのHostヘッダにはwww.example.comとなっていますから、これがドメイン名であることがわかります。

nginx 側の設定ファイルは以下としましょう。

http {
    server {
        listen 80;
        server_name www.example.com;
        root /var/www/example;

        location / {
            try_files $uri $uri/ =404;
        }
    }

    server {
        listen 80;
        server_name www.another-example.com;
        root /var/www/another-example;

        location / {
            try_files $uri $uri/ =404;
        }
    }
}

1つ目のserver_namewww.example.comと一致しますので、このブロックが選択されます。

なお、サーバーブロックが確定した後はrootに定義された/var/www/exampleというディレクトリに振り向けられます。

IPアドレス / ポート番号

ドメイン名が設定ファイルから見つけられなかった場合、このステップに進みます。

リクエストが送られたサーバーの「IPアドレスとポート番号の組み合わせ」と、nginx 設定ファイルの「listenディレクティブ」を比較して一致を確認しにいきます。

具体例

例えば、以下のようなリクエストが送られたとします。

GET / HTTP/1.1

このリクエストにはHostヘッダがありませんが、リクエスト元のサーバー情報が以下だったと仮定しましょう。

  • IPアドレス:xxx.xxx.x.xxx
  • ポート番号:8080

nginx 側の設定ファイルは以下としましょう。

http {
    # グローバル設定...

    server {
        listen xxx.xxx.x.xxx:8080;
        server_name _;
        root /var/www/default;

        location / {
            try_files $uri $uri/ =404;
        }
    }

    server {
        listen xxx.xxx.x.xxx:9090;
        server_name _;
        root /var/www/special;

        location / {
            try_files $uri $uri/ =404;
        }
    }
}

一つ目のサーバーブロックのlistenディレクティブと一致するので、これが選択されます。

つまり、/var/www/defaultディレクトリからコンテンツが提供されます。

上記に当てはまらない場合

「デフォルトサーバーブロック」を用意しておくことで、どのserver_nameディレクティブにも一致しない場合のアクセス先を決めておくことができます。

default_serverオプションをつけておくことで対応可能です。

具体例

例えば、以下のようなリクエストが送られたとします。

GET /about.html HTTP/1.1
Host: unknown.com

nginx 側の設定ファイルは以下としましょう。

http {
    # グローバル設定...

    server {
        listen 80 default_server;
        server_name _;
        root /var/www/default;

        location / {
            try_files $uri $uri/ =404;
        }
    }

    server {
        listen 80;
        server_name example.com;
        root /var/www/example;

        location / {
            try_files $uri $uri/ =404;
        }
    }
}

リクエストのHostヘッダにあるunknown.comがどこにも見当たりません。

ところが5行目のlistenディレクティブ内にdefault_serverオプションが設定されているので、消去法的に一つ目のサーバーブロックが選択されます。

つまり、/var/www/defaultディレクトリからコンテンツが提供されます。

3. リクエストの処理方法を決める

ここからはlocationディレクティブのお話です。

これまでの処理でドメイン部分が特定されましたので、このlocationディレクティブではURLのうちドメイン部分を除いたパスの部分を扱うステップになります。

パスの文字列パターンと振り向け先を定義しておき、それにヒットするとリクエストが振り向け先に流れていくイメージとなります。

基本的な書き方

以下はlocationディレクティブの基本的な構文です。

location /パターン/ {
    # 処理ルール
}

記入例を以下に示します。

http {
    # グローバル設定...

    server {
        listen 80;
        server_name example.com;
        root /var/www/html;

        location /images/ {
            root /var/www/image;
        }

        location /api/ {
            proxy_pass http://backend.example.com;
        }
    }
}

上記の例では、以下のルーティングがされます。

  • example.com/: /var/www/htmlにアクセス
  • example.com/images/: /var/www/imageにアクセス
  • example.com/api/: バックエンドサーバーにたらい回しする

修飾子の使い方

location/パターン/の間に「修飾子」をつけることができます。(オプションなので、なくても可)

修飾子とは、URIのパターンマッチングを細かく制御するものです。

修飾子には、以下の種類があります。

修飾子効果
= 完全一致
^~ 優先プレフィックスマッチ
~大文字小文字を区別「する」正規表現マッチ
~*大文字小文字を区別「しない」正規表現マッチ

イメージが湧きやすいよう、以下に具体例を挙げます。

完全一致 (=)

以下の場合、/exact/match.htmlだけにヒットします。

location = /exact/match.html {
    root /var/www/html;
}

優先プレフィックスマッチ (^~)

以下では、/images/で始まるすべてのリクエストに適用されます。

location ^~ /images/ {
    root /var/www/html;
}

例えば以下のようなリクエストがヒットします。

http://example.com/images/photo.jpg
http://example.com/images/2023/march/spring.png
http://example.com/images/

location/images/で始まるものが全てヒットするイメージです。

なお、正規表現でのマッチングよりも、このブロックが優先されます。

大文字小文字を区別する正規表現マッチ (~)

以下の場合には、末尾が.phpで終わるすべてのリクエストに適用されます。

location ~ \.php$ {
    fastcgi_pass unix:/var/run/php-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;
}

$は文字列の最後を表し、.は一文字以上の文字列を表します。

つまり、このパターンにヒットする URI は、例えば以下になります。

http://example.com/index.php
http://example.com/user/profile.php
http://example.com/api/v1/update.php

URIの最後にphpとあるものは、全てこのlocationディレクティブで処理されることになります。

なお大文字・小文字を「区別する」ので、”Php” などはヒットしません。

大文字小文字を区別しない正規表現マッチ (~*)

以下では、拡張子が.jpg, .jpeg, .pngのファイルへのリクエストに適用されます。

location ~* \.(jpg|jpeg|png)$ {
    root /var/www/images;
}

例えば、以下のような URI にマッチします。

http://example.com/photos/example.JPG
http://example.com/images/2023/march/spring.jpeg
http://example.com/assets/icons/icon.png

大文字小文字を区別しないため、.JPG, .JPEG, .PNGなどもマッチします。

4. 静的ファイルを探して返す

ここからは、リクエストの対象が「静的ファイル」の場合のお話です。

静的ファイルがリクエストされると、rootディレクティブで指定されたディレクトリから該当のファイルを探し、クライアントにレスポンスとして送信します。

処理の流れは以下の通りです。

  • リクエストの受信と対象ディレクトリの特定
  • ファイルの検索
  • レスポンスとして返す

上記の処理でファイルが見つからなかった場合にはエラーが返されます。

それぞれの処理を詳しく見ていきましょう。

この処理は自動的に行われる仕組みになっています。

リクエストの受信と対象ディレクトリの特定

まずは、静的ファイルを探すディレクトリを特定します。

この手順は、すでに以下のセクションで解説しました。

流れを軽く説明すると、以下になります。

まず、クライアントからリクエストが飛んできます。

続いて「リクエストヘッダ等の内容」と「nginx の設定ファイルのサーバーブロック」を比較して、一致するものを接続先サーバーとして決定。

さらに具体的にどのパスを探すかをlocationディレクティブから探し出します。

ファイルの検索

locationディレクティブ等に定義されているrootディレクティブを対象に、リクエスト URI に従ってファイルを検索します。

具体例

例えば、以下のようなリクエストを受け取ったとします。

http://example.com/images/picture.jpg

そして、以下がnginx の設定ファイルだとしましょう。

server {
    listen 80;
    server_name example.com;
    root /var/www/html;  # 全体のドキュメントルート

    location / {
        try_files $uri $uri/ =404;
    }

    location /images/ {
        try_files $uri $uri/ =404;  # ファイルが見つからない場合は404エラーを返す
    }
}

リクエストは/image/というパターンにマッチするので、10-12行目のlocationディレクティブで処理されます。

ファイルはサーバーの/var/www/html/images/というディレクトリから静的ファイルを探しにいくことになります。

もしも探しにいきたいディレクトリがimagesではなくspecialの場合には、以下のようにroot設定を追加します。

server {
    listen 80;
    server_name example.com;
    root /var/www/html;  # 全体のドキュメントルート

    location / {
        try_files $uri $uri/ =404;
    }

    location /images/ {
        root /var/www/html/special;  # 振り向け先のディレクトリを "special" にする
        try_files $uri $uri/ =404;
    }
}

レスポンスとして返す

取得された静的ファイルをレスポンスとして返します。

この静的ファイルの処理はアプリケーションサーバーを介さずに、Webサーバー(nginx)だけで完結します。

そのため、アプリケーションサーバーを介する場合に比べてパフォーマンス面でのメリットがあります。

5. プロキシ経由で処理する

これまではrootディレクティブというものを使いましたが、代わりにproxy_passというディレクティブを使うことができます。

ここではプロキシ経由で処理するための知識をお伝えします。

rootproxy_passの違い

それぞれの違いをごく簡単にまとめると、以下となります。

ディレクティブ処理の違い
rootnginx が存在するサーバー内のリソースを活用
proxy_pass他のサーバーに転送
root と proxy_pass の違い

つまり、proxy_passを使うとnginx が仲介役となって別のバックエンドサーバーにリクエストを転送することになります。

そして、バックエンドサーバーからのレスポンスを受け取り、クライアントに返すという流れです。

プロキシ設定の方法

以下により、/api/というパターンへのアクセスをバックエンドサーバーに転送します。

location /api/ {
    proxy_pass http://backend-server.com;
}

rootの代わりにproxy_passディレクティブに置き換える形になります。

6. 結果を返す

HTTP プロトコルに従い、クライアントにレスポンスを返却します。

レスポンスには以下の内容を含みます。

  • ステータスコード
  • ヘッダー
  • ボディデータ
  • 送信プロセス

それぞれ、解説します。

ステータスコード

ステータスコードはリクエストが成功したか、失敗したかを示すコードです。

以下の種類があります。

コード内容
2xxリクエストが成功して、レスポンスが返された
3xx他の URL にリダイレクト
4xxリクエストに問題があった
5xxサーバー側に問題があった
ステータスコードの意味

「404 Not Found」や「500 Internal Server Error」などはブラウザで表示されるので、目にした覚えのある方は多いのではないでしょうか。

ヘッダー

レスポンスに関する追加情報を提供するのがヘッダです。

例えば、以下のものを含みます。

  • コンテンツタイプ
  • キャッシュポリシー
  • セキュリティ関連の設定
  • クロスドメインポリシー
  • クッキー設定

クライアントに対して、レスポンスの処理方法などを支持するものになります。

ボディデータ

レスポンスのうち、データ部分に相当します。

具体的には、以下のようなものがあります。

  • HTML文書
  • 画像データ
  • JSON / XMLなどのデータ
  • テキストデータ

リクエストに応じて様々なボディデータが返却されます。

送信プロセス

以上の内容を組み合わせて HTTP レスポンスを構築して、TCP/IP 経由でクライアントにレスポンスを送信します。

クライアントはレスポンスを受け取ると、以下の処理を行います。

  • ステータスコードを元に取るべき措置を決定
  • ヘッダーを元にレスポンスデータを処理

以上が nginx の内部で行われているルーティングの流れでした。

まとめ

文章だと長文になってしまいましたが、普段 Web アプリケーションを作っている方なら「あぁ、ここがこう繋がっているんだな!」と合点がいくポイントが結構あると思います。

本記事では基礎編だったのでベーシックな内容に留めています。

ぜひ理解を深め、nginx を自在に使えるようになりましょう!

この記事が気に入ったら
フォローしてね!

シェア・記事の保存はこちら!

この記事を書いた人

karo@プログラマのアバター karo@プログラマ プログラマ

「書くことで人の役にたつ」をモットーに活動中。
本職はプログラマで、Pythonが得意。
基本情報技術者試験合格。

コメント

コメントする

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)