0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PHP には TLS 機能があるのに、なぜビルトインサーバーは HTTPS で動かないのか ――embed SAPI と Stream API の PoC から学ぶ php -S の実装と改善

0
Last updated at Posted at 2026-07-20

対象読者

この記事は、php -Sを利用しているPHP開発者や、php-src を読んでビルトインサーバーの仕組みと改善方法を学びたい人を対象としています。

C 言語や Zend Engine の詳しい知識は前提にせず、TLS、PHP Stream API、embed SAPI を題材に、既存機能の調査、PoC による実現可能性の確認、責務分離の考え方を扱います。

PoC (Proof of Concept) のコード

src/http_server_tls.c が今回の目的のために作成したコードです。

embed SAPI と Stream API の PoC から学ぶ php -S の実装と改善

PHPで小さなアプリケーションを確認するとき、ビルトインサーバーは便利です。

ターミナルからphp -Sを起動すれば、ApacheやNginxを用意しなくても、PHPスクリプトや静的ファイルをブラウザーから確認できます。

PHP公式マニュアルでも、ビルトインサーバーはアプリケーション開発、テスト、管理された環境でのデモを支援する機能とされています。一方で、本格的なWebサーバーではなく、公開ネットワークで使うものではないとも明記されています。

それでも、開発用サーバーだからこそ困ることがあります。

php -SはHTTPSで起動できません。

今回は、PHPビルトインサーバーを本番サーバーに変えるのではなく、PHPがすでに持っている機能を使って、TLS対応の基礎部分を作れるのかを調べました。

その過程で、PHP Stream API、embed SAPI、Debianのlibphp-embed、そしてFrankenPHPへつながる学習材料が見えてきました。

localhost では動くのに、スマートフォンでは動かない

HTTPSが必要になる理由として分かりやすいのが、ブラウザーのSecure Contextです。

現代のブラウザーでは、端末や個人情報へ強くアクセスするWeb APIの多くが、安全なコンテキストでしか利用できません。

代表例には、次のようなものがあります。

  • Service WorkerとPWA
  • カメラやマイク
  • WebAuthnとパスキー
  • Clipboard API
  • 位置情報

ただし、ローカル開発には例外があります。

http://localhosthttp://127.0.0.1http://*.localhostは、HTTPSでなくても「潜在的に信頼できるオリジン」として扱われます。そのため、同じPCのブラウザーで確認している間は、HTTPでもService Workerなどを利用できることがあります。

問題になるのは、スマートフォンや別のPCから確認するときです。

開発用PCでビルトインサーバーをLANへ公開し、スマートフォンからhttp://192.168.x.x:8000のようなアドレスへアクセスしても、そのアドレスはスマートフォン自身のlocalhostではありません。

PHPビルトインサーバーは外部インターフェースで待ち受けることもできますが、HTTP接続のままです。

カメラを使ったQRコード読み取りや、PWAの実機確認をしたい場合、ここでlocalhostの例外が使えなくなります。

PHP には TLS 機能がすでにある

PHPビルトインサーバーがHTTPSに対応していないと聞くと、PHPにサーバー側のTLS機能がないように思えます。

しかし、PHP Stream APIはファイルだけを扱う仕組みではありません。

TCPやTLSなどの通信も、同じStream APIの枠組みから扱えます。

TLS用のStream Contextには、ローカル証明書を指定するlocal_certや、秘密鍵を別ファイルとして指定するlocal_pkなどの設定があります。ssl://tls://トランスポートに対して、証明書や検証方法を設定できる仕組みが、PHPにはすでに用意されています。

つまり、今回の問いは次のように変わります。

「PHPにTLS機能を追加できるか」ではありません。

「PHPがすでに持っているTLS機能を、ビルトインサーバーの実装から利用できるか」です。

この言い換えは重要です。

OpenSSLを直接操作する新しい暗号化コードを作る必要はありません。すでに存在するPHP Stream APIを使える構造を考えればよいからです。

PHP はコマンドだけではなく、ライブラリでもある

PHPは通常、コマンドやWebサーバーから起動します。

CLIならphpコマンド、WebアプリケーションならPHP-FPMを思い浮かべる人が多いでしょう。

これらの実行環境とPHPの実行エンジンをつなぐ入口がSAPIです。

SAPIはServer Application Programming Interfaceの略です。

代表的なSAPIには、次のようなものがあります。

SAPI PHPを起動する環境
CLI phpコマンド
FPM FastCGIサーバー
CLI Server php -S
embed Cなどのホストプログラム

embed SAPIでは、PHPが単独でプロセスの中心になるのではありません。

Cプログラムなどがホストになり、その中へPHPランタイムをライブラリとして組み込みます。

PHP拡張と比較すると、呼び出す方向が逆です。

PHP拡張は、PHPからCを呼びます。
embed SAPIは、CからPHPを呼びます。

php-srcには、embed SAPIの初期化、PHP内部関数の呼び出し、PHPスクリプトの実行方法を説明するREADMEがあります。

ただし、通常のPHPマニュアルを読んでいるだけでは、なかなか見つけにくい資料です。

READMEはphp-srcのソースツリー内にあります。

PHPをライブラリとして使えることを知らなければ、そもそも「embed SAPI」という言葉で検索することもありません。

Debian では libphp-embed が必要

Debianでembed SAPIを利用するには、通常のphp-cliに加えてlibphp-embedが必要です。Cプログラムをビルドする場合は、PHPのヘッダーやphp-configを含むphp-devも導入します。

php-srcには​sapi/embed/README.mdとして使い方が用意されています。ただし、PHPマニュアル本体ではなくソースツリー内にあるため、embed SAPIという名称を知らないと見つけにくい資料です。

embed SAPI と Stream API で TLS 通信を試す

今回作成したPoC (Proof of Concept)では、embed SAPI を使って PHP ランタイムを初期化し、その中から PHP Stream API を呼び出しました。

処理の流れは次のようになります。

  1. ホストとなるCプログラムを起動する
  2. embed SAPIでPHPランタイムを初期化する
  3. TLS用のStream Contextを作る
  4. 証明書と秘密鍵を設定する
  5. TLS接続を待ち受ける
  6. 接続を受け付ける
  7. 復号されたHTTPリクエストを読む
  8. HTTPレスポンスをTLS経由で返す

PoCでは、Stream Contextに証明書と秘密鍵を設定し、tls://127.0.0.1:8443で接続を待ち受けます。受信したデータをPHP Stream APIで読み、固定のHTTP/1.1レスポンスを返します。

TLSハンドシェイクや暗号化処理はOpenSSL APIを直接操作せず、PHP Stream APIへ任せています。

PoCの目的は完成品ではなく、TLS通信がPHPの既存機能で成立することの確認です。

FrankenPHP も PHP をプロセスへ組み込んでいる

embed SAPI は、昔の実験的な仕組みとして残っているだけではありません。

現在の実用例として挙げられるのが FrankenPHP です。

FrankenPHP のビルド手順では、PHP を --enable-embed と ZTS を有効にして構築し、共有ライブラリとしてGo側のプログラムへリンクします。

ただし、「FrankenPHP は embed SAPI をそのまま使っている」と説明するのは正確ではありません。

FrankenPHP は PHP インタープリターを CGO 経由で Go へ直接組み込み、C 側に独自の SAPI 実装を持っています。

Go側はリクエストの振り分けやスレッドプールを管理し、C 側は PHP SAPI のライフサイクル、PHP スクリプトの実行、スーパーグローバルの構築などを担当します。

つまり、FrankenPHP は次の二段階を実装しています。

  • libphp を使って PHP ランタイムをプロセスへ組み込む
  • Web サーバーと PHP を接続する独自SAPIを実装する

この事例から分かるのは、PHP を別プロセスの PHP-FPM として動かすだけでなく、Web サーバーと同じプロセスへ組み込む設計も現役で使われていることです。

embed SAPI は完成したWebサーバーではありません。
PHP を別の実行環境へ組み込むための入口です。

参考

まとめ

今回の PoC では、embed SAPI から PHP Stream API を利用し、TLS 接続を受け付けて HTTP レスポンスを返せることを確認しました。

この結果から分かるのは、php -S が HTTPS に対応していない理由は、PHP に TLS 機能がないからではないということです。課題は、既存のビルトインサーバーが、その機能を利用しやすい構造になっていない点にあります。

TLS 対応を進めるには、通信、HTTP 解析、I/O 待機、PHP 実行の責務を分け、それぞれを独立して変更できる境界が必要です。

実現可能性は確認できました。次に必要なのは、維持可能な変更単位へ分解することです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?