リクエストとファイルは関係ないという事実

HTTPを単純に使うと、主に静的なファイルに対する送受信をイメージしがちです。 ですが、実はそれは誤ったイメージです。 実際は、リクエストとファイルパスに関しての関連性は特にありません。 イメージしやすいし、用途として実際に使っているのでそうなっているだけです。

静的ファイル送受信という意味での利用

静的ファイル送受信という目的においては、一般的に ドキュメントルート(Document Root) というものが重要視されます。これは、リクエストにあったパスをサーバー上のドキュメントに変換するための基底部と考えられます。

わかりやすい例として、Apache httpd(デファクトスタンダード的扱いのWebサーバー実装)の設定を見てみましょう。

$ sudo apt update
$ sudo apt install -y apache2 # Apache HTTP Serverをインストール
$ cat /etc/apache2/sites-available/000-default.conf | grep DocumentRoot
    DocumentRoot /var/www/html # ここがドキュメントルート
$ ls /var/www/html             # そのディレクトリをチェックすると…
index.html                     # 実際にファイルがある

この場合、仮にサーバー名が example.comの場合、 http://example.com/index.html にアクセスすることで、/var/www/html/index.html にアクセスすることになります。

パスがディレクトリとみなされる場合の対応

リクエストされたパスがディレクトリであるとみなされる場合、Webサーバーは通常、そのディレクトリ内の index.html などのデフォルトのファイルを返します。これにより、ユーザーは明示的にファイル名を指定しなくても、ディレクトリにアクセスできるようになります。 なお、 この処理はHTTPの仕様ではなく、あくまでWebサーバー上での実装に基づきます 。そのため、実際に読み込まれるファイルがなんなのかはWebサーバーの設定次第となります[1]

対象となる拡張子の追加

たとえばPHP言語のように、サーバー上で動的にコンテンツを生成するシステムを組み込んだ場合、それがわかるようにするために、ファイル名の末尾(いわゆる拡張子)を言語に合わせて設定する必要があります。 すなわち、指定の拡張子のファイルがマップされた場合に、その拡張子に対応した処理を行えるシステムに処理を委譲するという形をとります。

sequenceDiagram participant ブラウザ as ブラウザ participant Webサーバー as Webサーバー participant PHPランタイム as PHPランタイム ブラウザ->>Webサーバー: HTTPリクエスト (例: /index.php または /style.css) alt 静的ファイルの場合 Webサーバー->>Webサーバー: ドキュメントルートでファイルを探索 Webサーバー-->>ブラウザ: 静的ファイルを返す (200 OK) else 動的(PHP)の場合 Webサーバー->>PHPランタイム: FastCGI/HTTPプロキシでスクリプト実行要求 PHPランタイム->>PHPランタイム: PHPスクリプト実行(DB/キャッシュ呼び出しなど) PHPランタイム-->>Webサーバー: レンダリング済みレスポンスを返す (ヘッダ+ボディ) Webサーバー-->>ブラウザ: クライアントへHTTPレスポンスを返す end

Apache httpdの場合、PHPにどう渡すかについては大きく2つの考え方が存在します。

  • httpd自体にPHP言語ランタイムを組み込む(prefork)

    • 内部的に渡すだけのため呼び出しが高速ですが、httpd自体のプロセスが肥大化します。

  • httpdからソケット通信で起動しているPHP言語ランタイムにリクエストを委譲する(worker/event)

    • リクエストを別プロセスに渡すため、httpdのプロセスは軽量ですが、呼び出しにかかるオーバーヘッドが増加します。

    • ただしリクエストで使用するコード自体は共有されるため、メモリ使用量は抑えられますし、一度動かしているコード(かつ変更無し)ならばキャッシュされているためにすぐに機能するようになっています。

一般的なPHPのコードはどちらの方式でも対応しているため、開発時はpreforkモードで手軽に開発を行い、運用時はworker/eventで動かすといった処理も可能です。

パスとファイルが一致しない場合

たとえばAPIサーバーのように、実際のファイルは存在せずに、HTTPリクエストにおけるパスにより対応を振り分けるといったケースも非常に多いです。 たとえば、以下のようなパスを持つAPIがあるとします。

  • http://example.com/api/users

  • http://example.com/api/products/42

この時、受け取ったWebサーバー側がパス(/api/users/api/products/42)を一旦解釈します。 そしてそのパスに見合ったプログラムを呼び出すことで、適切なレスポンスを生成することも可能です。

  • /api/users -> ユーザー一覧をJSONで返す

  • /api/products/42 -> 商品IDが42の商品の詳細をJSONで返す

この場合、リクエストに応じて都度プログラムが実行されると、Pythonのようなインタプリタ型を伴う言語では応答が悪くなる恐れがあります。そのため、事前にスクリプトを起動した状態にしておき、バックエンドで呼び出すことが多いです。 この呼び出し方はある程度標準化されており、FastCGIやHTTPプロキシを使うことが多いです。

sequenceDiagram participant ブラウザ as ブラウザ participant Webサーバー as Webサーバー participant Pythonランタイム as Pythonランタイム
(起動済み) ブラウザ->>Webサーバー: HTTPリクエスト (例: /api/users) Webサーバー->>Pythonランタイム: FastCGI/HTTPプロキシでスクリプト実行要求 Pythonランタイム->>Pythonランタイム: スクリプト実行(DB/キャッシュ呼び出しなど) Pythonランタイム-->>Webサーバー: レンダリング済みレスポンスを返す (ヘッダ+ボディ) Webサーバー-->>ブラウザ: クライアントへHTTPレスポンスを返す

最近では、クライアント(ブラウザ)側でJavaScriptによる動的なページレンダリングをしつつ、裏側で部品となる情報をAPIリクエストでやりとりして、取得したデータに基づいてページを書き換えていくという方式が一般的です。 X(旧Twitter)やInstagram、GMailなどの主要なWebサービスはこのような方式で動いています。 特にこの方式でURLが変わらずにページ内容が書き換わっていく動きについては、SPA(Single Page Application)と呼ばれています。

注釈

PWA(Progressive Web Apps)という、Webアプリケーションをネイティブアプリケーションのように振る舞わせる技術もあります。SPAの応用で実現されており、オフラインでの動作やプッシュ通知なども可能です。

また、スマートフォンアプリについても、ネイティブアプリケーションの中でWebViewコンポーネントを用いてWebページをアプリの一部として組み込むという運用も存在し、現状ではあまり出てきませんが一時「皮(がわ)ネイティブ」と表現されることもありました(最近はほとんど聞きません、現在はほとんど使われない俗語です)。

データ生成の具体的な実装について

上記で説明したようなファイルに対応しない動的な処理について、具体的な実装技術やパフォーマンス課題については、別ファイル path-vs-file.md で詳しく扱います。