Created
June 21, 2025 19:55
-
-
Save masakielastic/3d13a5906ba5e57dcda9516971d0e52c to your computer and use it in GitHub Desktop.
PHP libuv 拡張 (amphp/ext-uv) で 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 | |
| // PHP libuv拡張を使ったシンプルなHTTP/1.1サーバー | |
| function createHttpResponse($statusCode, $statusText, $body, $headers = []) { | |
| $response = "HTTP/1.1 {$statusCode} {$statusText}\r\n"; | |
| // デフォルトヘッダー | |
| $defaultHeaders = [ | |
| 'Content-Type' => 'text/html; charset=utf-8', | |
| 'Content-Length' => strlen($body), | |
| 'Connection' => 'close', | |
| 'Server' => 'PHP-libuv/1.0' | |
| ]; | |
| $allHeaders = array_merge($defaultHeaders, $headers); | |
| foreach ($allHeaders as $name => $value) { | |
| $response .= "{$name}: {$value}\r\n"; | |
| } | |
| $response .= "\r\n{$body}"; | |
| return $response; | |
| } | |
| function parseHttpRequest($data) { | |
| $lines = explode("\r\n", $data); | |
| $requestLine = array_shift($lines); | |
| if (empty($requestLine)) { | |
| return null; | |
| } | |
| $parts = explode(' ', $requestLine); | |
| if (count($parts) < 3) { | |
| return null; | |
| } | |
| $method = $parts[0]; | |
| $uri = $parts[1]; | |
| $version = $parts[2]; | |
| $headers = []; | |
| foreach ($lines as $line) { | |
| if (empty($line)) { | |
| break; | |
| } | |
| $colonPos = strpos($line, ':'); | |
| if ($colonPos !== false) { | |
| $name = trim(substr($line, 0, $colonPos)); | |
| $value = trim(substr($line, $colonPos + 1)); | |
| $headers[strtolower($name)] = $value; | |
| } | |
| } | |
| return [ | |
| 'method' => $method, | |
| 'uri' => $uri, | |
| 'version' => $version, | |
| 'headers' => $headers | |
| ]; | |
| } | |
| function handleRequest($request) { | |
| $method = $request['method']; | |
| $uri = $request['uri']; | |
| echo "Request: {$method} {$uri}\n"; | |
| switch ($uri) { | |
| case '/': | |
| return createHttpResponse(200, 'OK', | |
| '<h1>Welcome to PHP libuv Server!</h1>' . | |
| '<p>Current time: ' . date('Y-m-d H:i:s') . '</p>' . | |
| '<p><a href="/hello">Hello page</a></p>' . | |
| '<p><a href="/json">JSON API</a></p>' | |
| ); | |
| case '/hello': | |
| return createHttpResponse(200, 'OK', | |
| '<h1>Hello, World!</h1>' . | |
| '<p><a href="/">Back to home</a></p>' | |
| ); | |
| case '/json': | |
| $data = json_encode([ | |
| 'message' => 'Hello from PHP libuv server', | |
| 'timestamp' => time(), | |
| 'method' => $method | |
| ]); | |
| return createHttpResponse(200, 'OK', $data, [ | |
| 'Content-Type' => 'application/json' | |
| ]); | |
| default: | |
| return createHttpResponse(404, 'Not Found', | |
| '<h1>404 - Page Not Found</h1>' . | |
| '<p>The requested page was not found.</p>' . | |
| '<p><a href="/">Back to home</a></p>' | |
| ); | |
| } | |
| } | |
| // TCPサーバーを作成 | |
| $server = uv_tcp_init(); | |
| // サーバーをバインド | |
| $bind_result = uv_tcp_bind($server, uv_ip4_addr('127.0.0.1', 8080)); | |
| if ($bind_result !== 0) { | |
| echo "Warning: Bind returned code $bind_result (but may still work)\n"; | |
| } | |
| // リスナーを開始 | |
| $listen_result = uv_listen($server, 100, function($server) { | |
| $client = uv_tcp_init(); | |
| $accept_result = uv_accept($server, $client); | |
| if ($accept_result !== 0) { | |
| echo "Warning: Accept returned code $accept_result (but may still work)\n"; | |
| } | |
| echo "New connection accepted\n"; | |
| // クライアントからのデータを読み取り | |
| // この拡張では引数が2つの場合があります | |
| uv_read_start($client, function($client, $data) { | |
| if (!empty($data)) { | |
| echo "Received data: " . substr($data, 0, 100) . "...\n"; | |
| // HTTPリクエストをパース | |
| $request = parseHttpRequest($data); | |
| if ($request) { | |
| // リクエストを処理してレスポンスを生成 | |
| $response = handleRequest($request); | |
| // レスポンスを送信 | |
| uv_write($client, $response, function($client, $status) { | |
| if ($status == 0) { | |
| echo "Response sent successfully\n"; | |
| } else { | |
| echo "Failed to send response: $status\n"; | |
| } | |
| uv_close($client, function() { | |
| echo "Client connection closed\n"; | |
| }); | |
| }); | |
| } else { | |
| // 不正なリクエストの場合 | |
| echo "Invalid request received\n"; | |
| $errorResponse = createHttpResponse(400, 'Bad Request', | |
| '<h1>400 - Bad Request</h1>' | |
| ); | |
| uv_write($client, $errorResponse, function($client, $status) { | |
| uv_close($client, function() { | |
| echo "Error response sent and connection closed\n"; | |
| }); | |
| }); | |
| } | |
| } else { | |
| // 接続が閉じられた場合またはデータが空の場合 | |
| echo "Connection closed by client or empty data\n"; | |
| uv_close($client, function() { | |
| echo "Client connection properly closed\n"; | |
| }); | |
| } | |
| }); | |
| }); | |
| echo "HTTP Server started on http://127.0.0.1:8080\n"; | |
| echo "Press Ctrl+C to stop the server\n"; | |
| // シグナルハンドラーを設定(Ctrl+Cで停止) | |
| $signal = uv_signal_init(); | |
| uv_signal_start($signal, function() use ($server) { | |
| echo "\nShutting down server...\n"; | |
| uv_close($server); | |
| uv_stop(); | |
| }, SIGINT); | |
| // イベントループを開始 | |
| uv_run(); | |
| echo "Server stopped\n"; | |
| ?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment