Skip to content

Instantly share code, notes, and snippets.

@masakielastic
Created July 10, 2025 20:47
Show Gist options
  • Save masakielastic/9a9c6ee0af5711543e4a1aa1dc56ce74 to your computer and use it in GitHub Desktop.
Save masakielastic/9a9c6ee0af5711543e4a1aa1dc56ce74 to your computer and use it in GitHub Desktop.
stream_socket_server で TLS 対応の HTTP/1 サーバー。ハンドラー形式。
<?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