feat(cloud): Http Request/Response primitives

This commit is contained in:
유창욱 2026-07-11 23:54:58 +09:00
parent 7246e12c4a
commit a0db1e06ca
3 changed files with 82 additions and 0 deletions

View file

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\Http;
final class Request
{
private array $headers;
public function __construct(
private string $method, private string $path, array $headers,
private string $body, private array $query = [], private array $post = []
) {
$this->headers = [];
foreach ($headers as $k => $v) $this->headers[strtolower($k)] = $v;
}
public function method(): string { return $this->method; }
public function path(): string { return $this->path; }
public function header(string $name): ?string { return $this->headers[strtolower($name)] ?? null; }
public function body(): string { return $this->body; }
public function json(): array { $d = json_decode($this->body, true); return is_array($d) ? $d : []; }
public function query(string $k): ?string { return isset($this->query[$k]) ? (string)$this->query[$k] : null; }
public function post(string $k): ?string { return isset($this->post[$k]) ? (string)$this->post[$k] : null; }
public static function fromGlobals(): self
{
$headers = function_exists('getallheaders') ? (getallheaders() ?: []) : [];
return new self(
$_SERVER['REQUEST_METHOD'] ?? 'GET',
parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/',
$headers, file_get_contents('php://input') ?: '', $_GET, $_POST
);
}
}

View file

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Http;
final class Response
{
private function __construct(private int $status, private array $headers, private string $body) {}
public static function json(array $data, int $status = 200): self {
return new self($status, ['Content-Type' => 'application/json'], json_encode($data, JSON_UNESCAPED_UNICODE));
}
public static function text(string $s, int $status = 200): self {
return new self($status, ['Content-Type' => 'text/html; charset=utf-8'], $s);
}
public static function bytes(string $b, string $contentType, int $status = 200): self {
return new self($status, ['Content-Type' => $contentType], $b);
}
public static function redirect(string $to): self {
return new self(302, ['Location' => $to], '');
}
public function status(): int { return $this->status; }
public function headers(): array { return $this->headers; }
public function body(): string { return $this->body; }
public function send(): void {
http_response_code($this->status);
foreach ($this->headers as $k => $v) header("$k: $v");
echo $this->body;
}
}

21
cloud/tests/HttpTest.php Normal file
View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace App\Tests;
use App\Http\{Request, Response};
use PHPUnit\Framework\TestCase;
final class HttpTest extends TestCase
{
public function testRequestJsonAndHeaderCaseInsensitive(): void {
$r = new Request('POST', '/api/events', ['X-Nonce' => 'n1'], '{"a":1}', [], []);
$this->assertSame('POST', $r->method());
$this->assertSame('n1', $r->header('x-nonce'));
$this->assertSame(['a' => 1], $r->json());
}
public function testResponseJson(): void {
$res = Response::json(['ok' => true], 201);
$this->assertSame(201, $res->status());
$this->assertStringContainsString('application/json', $res->headers()['Content-Type']);
$this->assertSame('{"ok":true}', $res->body());
}
}