Created
July 10, 2025 20:47
-
-
Save masakielastic/9a9c6ee0af5711543e4a1aa1dc56ce74 to your computer and use it in GitHub Desktop.
stream_socket_server で TLS 対応の HTTP/1 サーバー。ハンドラー形式。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// --- 1. 自己署名証明書+秘密鍵を生成 --- // | |
// OpenSSL 設定(鍵長・鍵タイプ等) | |
$config = [ | |
"private_key_bits" => 2048, | |
"private_key_type" => OPENSSL_KEYTYPE_RSA, | |
]; | |
// 1.1. 秘密鍵を生成 | |
$privateKey = openssl_pkey_new($config); | |
if ($privateKey === false) { | |
die("秘密鍵の生成に失敗: " . openssl_error_string() . "\n"); | |
} | |
// 1.2. CSR を生成(DN 情報は適宜変更してください) | |
$dn = [ | |
"countryName" => "JP", | |
"stateOrProvinceName" => "Tokyo", | |
"localityName" => "Chiyoda-ku", | |
"organizationName" => "Example Co., Ltd.", | |
"organizationalUnitName" => "IT", | |
"commonName" => "localhost", | |
"emailAddress" => "admin@localhost", | |
]; | |
$csr = openssl_csr_new($dn, $privateKey, $config); | |
if ($csr === false) { | |
die("CSR の生成に失敗: " . openssl_error_string() . "\n"); | |
} | |
// 1.3. CSR を自己署名して証明書を生成(365日有効) | |
$validDays = 365; | |
$x509 = openssl_csr_sign($csr, null, $privateKey, $validDays, $config); | |
if ($x509 === false) { | |
die("証明書の署名に失敗: " . openssl_error_string() . "\n"); | |
} | |
// 1.4. PEM 文字列として取得 | |
openssl_pkey_export($privateKey, $privateKeyPem); | |
openssl_x509_export($x509, $certPem); | |
// 1.5. 証明書 + 鍵 をまとめた PEM ファイルを一時ディレクトリに書き出し | |
$pemFile = sys_get_temp_dir() . '/selfsigned-' . uniqid() . '.pem'; | |
$pemCombined = $certPem . "\n" . $privateKeyPem; | |
if (file_put_contents($pemFile, $pemCombined) === false) { | |
die("PEM ファイルの書き出しに失敗\n"); | |
} | |
/** | |
* レスポンスオブジェクトの初期化 | |
*/ | |
function http_response_init(): array { | |
return [ | |
'status' => 200, | |
'headers' => [], | |
'body' => '', | |
]; | |
} | |
/** | |
* ステータスコード設定 | |
*/ | |
function http_response_status(array &$res, int $status): void { | |
$res['status'] = $status; | |
} | |
/** | |
* ヘッダー追加 | |
*/ | |
function http_response_header(array &$res, string $field, string $value): void { | |
$res['headers'][$field] = $value; | |
} | |
/** | |
* ボディ設定 | |
*/ | |
function http_response_body(array &$res, string $body): void { | |
$res['body'] = $body; | |
} | |
/** | |
* クライアントに書き込み、接続を閉じる | |
*/ | |
function http_respond($client, array $res): void { | |
// ステータス行 | |
$statusText = [ | |
200 => 'OK', | |
404 => 'Not Found', | |
500 => 'Internal Server Error', | |
// 必要に応じて追加 | |
][$res['status']] ?? ''; | |
$out = sprintf("HTTP/1.1 %d %s\r\n", $res['status'], $statusText); | |
// ヘッダー行 | |
foreach ($res['headers'] as $k => $v) { | |
$out .= "{$k}: {$v}\r\n"; | |
} | |
// 空行 + ボディ | |
$out .= "\r\n" . $res['body']; | |
fwrite($client, $out); | |
fclose($client); | |
} | |
// --- 2. リクエストハンドラー --- // | |
// C の handle_request を模した形式 :contentReference[oaicite:0]{index=0} | |
function handle_request($client, string $method, string $path): void { | |
$res = http_response_init(); | |
http_response_status($res, 200); | |
http_response_header($res, 'Content-Type', 'text/html; charset=UTF-8'); | |
$body = "<html><body><h1>こんにちは、世界!</h1>" | |
. "<p>あなたは {$method} {$path} をリクエストしました。</p>" | |
. "</body></html>"; | |
http_response_body($res, $body); | |
http_respond($client, $res); | |
} | |
// --- 3. サーバ起動/受信ループ --- :contentReference[oaicite:1]{index=1} | |
// (証明書生成~$server の準備部分は省略せずそのまま残してください) | |
$context = stream_context_create([ | |
'ssl' => [ | |
'local_cert' => $pemFile, | |
'allow_self_signed' => true, | |
'verify_peer' => false, | |
'verify_peer_name' => false, | |
], | |
]); | |
$server = stream_socket_server( | |
'tls://0.0.0.0:8443', | |
$errno, | |
$errstr, | |
STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, | |
$context | |
); | |
if (!$server) { | |
die("サーバの起動に失敗: $errstr ($errno)\n"); | |
} | |
echo "Listening on https://0.0.0.0:8443\n"; | |
while ($client = @stream_socket_accept($server, -1)) { | |
// ヘッダ読み込み | |
$req = ''; | |
while (!feof($client) && strpos($req, "\r\n\r\n") === false) { | |
$req .= fgets($client, 1024); | |
} | |
// メソッドとパス抜き出し | |
if (preg_match('#^(GET|POST)\s+([^\s]+)\s+HTTP/1\.[01]#', $req, $m)) { | |
[, $method, $path] = $m; | |
} else { | |
$method = 'GET'; | |
$path = '/'; | |
} | |
// ハンドラー呼び出し | |
handle_request($client, $method, $path); | |
} | |
fclose($server); | |
// ※必要に応じて @unlink($pemFile) で一時ファイルを削除 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment