$argv[2], 'key' => $argv[3], 'alpn' => ['http/1.1']] : null; $server = @stream_socket_server("tcp://127.0.0.1:{$port}", $errno, $errstr); if (!$server) { fwrite(STDOUT, "error {$errstr}\n"); exit(1); } fwrite(STDOUT, "ready\n"); fflush(STDOUT); $count = []; // テストが次のレスポンスを決められる(POST /__next)、最後のリクエストを確認出来る(GET /__last) $next = null; $last = null; /** 生のHTTPレスポンス */ function response(int $code, string $body, array $headers = []): string { $reason = [200 => 'OK', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 401 => 'Unauthorized', 404 => 'Not Found', 502 => 'Bad Gateway'][$code] ?? 'Status'; $out = "HTTP/1.1 {$code} {$reason}\r\n"; $headers += ['Content-Type' => 'application/json', 'Content-Length' => strlen($body), 'Connection' => 'close']; foreach ($headers as $k => $v) $out .= "{$k}: {$v}\r\n"; return $out."\r\n".$body; } function json(int $code, array $data): string { return response($code, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); } while (true) { $c = @stream_socket_accept($server, 60); if (!$c) break; // 60秒何も来なければ終了(テストが落ちても残らない様に) stream_set_timeout($c, 5); // HTTPS(最初のバイトがTLSのハンドシェイク) if ($tls && stream_socket_recvfrom($c, 1, STREAM_PEEK) === "\x16") { try { $c = Tls::accept($c, $tls); } catch (\Throwable $e) { @fclose($c); continue; } } // リクエストの読み込み $head = ''; while (!str_contains($head, "\r\n\r\n") && ($line = fgets($c)) !== false) $head .= $line; [$reqLine] = explode("\r\n", $head, 2); $parts = explode(' ', $reqLine); $method = $parts[0] ?? ''; $target = $parts[1] ?? '/'; $headers = []; foreach (array_slice(explode("\r\n", trim($head)), 1) as $h) { if (!str_contains($h, ':')) continue; [$k, $v] = explode(':', $h, 2); $headers[strtolower(trim($k))] = trim($v); } $body = ''; $len = (int)($headers['content-length'] ?? 0); while (strlen($body) < $len && !feof($c)) { $chunk = fread($c, $len - strlen($body)); if ($chunk === false || $chunk === '') break; $body .= $chunk; } $path = parse_url($target, PHP_URL_PATH) ?: '/'; parse_str((string)parse_url($target, PHP_URL_QUERY), $query); $count[$path] = ($count[$path] ?? 0) + 1; $req = ['method' => $method, 'target' => $target, 'headers' => $headers, 'body' => $body]; if ($path === '/__quit') { fclose($c); break; } $bearer = ($headers['authorization'] ?? '') === 'Bearer tok123'; // テスト用の操作(記録しない) if ($path === '/__next') { $next = json_decode($body, true); @fwrite($c, json(200, ['ok' => true])); fclose($c); continue; } if ($path === '/__last') { @fwrite($c, json(200, $last ?? [])); fclose($c); continue; } $last = ['method' => $method, 'path' => $path, 'query' => (string)parse_url($target, PHP_URL_QUERY), 'body' => $body]; $out = match (true) { // Curl用 $path === '/echo' => json(200, $req), $path === '/count' => json(200, ['count' => $count[$query['path'] ?? ''] ?? 0]), $path === '/chunked' => "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n" ."7\r\nHello, \r\n6\r\nworld!\r\n0\r\n\r\n", $path === '/redirect' => response(302, '', ['Location' => $query['to'] ?? '/echo']), $path === '/redirect-303' => response(303, '', ['Location' => '/echo']), $path === '/redirect-301' => response(301, '', ['Location' => 'echo?from=301']), $path === '/loop' => response(302, '', ['Location' => '/loop']), $path === '/big' => response(200, str_repeat('x', 200000), ['Content-Type' => 'text/plain']), $path === '/slow' => (function() { sleep(3); return response(200, 'late'); })(), $path === '/missing' => json(404, ['desc' => 'Not found']), // Openprovider用(/v1beta) $path === '/v1beta/auth/login' => (function() use ($body) { $in = json_decode($body, true); return is_array($in) && isset($in['username'], $in['password']) ? json(200, ['code' => 0, 'data' => ['token' => 'tok123', 'reseller_id' => 42]]) : json(401, ['code' => 196, 'desc' => 'Authentication/Authorization Failed']); })(), $path === '/bad/v1beta/auth/login' => json(401, ['code' => 196, 'desc' => 'Authentication/Authorization Failed']), // /__nextで決めたレスポンス(1回だけ) $next !== null && str_starts_with($path, '/v1beta/') && $bearer => (function() use (&$next) { $out = json((int)($next['status'] ?? 200), (array)($next['body'] ?? [])); $next = null; return $out; })(), $path === '/v1beta/domains/check' && $bearer => (function() use ($body) { $in = json_decode($body, true); $results = []; foreach ($in['domains'] ?? [] as $d) { $name = "{$d['name']}.{$d['extension']}"; $results[] = $name === '076.moe' ? ['domain' => $name, 'status' => 'active', 'reason' => 'In use'] : ['domain' => $name, 'status' => 'free']; } return json(200, ['code' => 0, 'data' => ['results' => $results]]); })(), $path === '/v1beta/domains' && $method === 'GET' && $bearer => json(200, ['code' => 0, 'data' => ['results' => [['id' => 1, 'domain' => ['name' => '076', 'extension' => 'moe']]], 'total' => 1]]), $path === '/v1beta/gateway' => response(502, 'Bad Gateway', ['Content-Type' => 'text/html']), str_starts_with($path, '/v1beta/') && !$bearer => json(401, ['code' => 196, 'desc' => 'Authentication/Authorization Failed']), default => json(404, ['desc' => "No route: {$path}"]), }; @fwrite($c, $out); fclose($c); }