Click the Castor logo or press Ctrl Alt T to change theme.
# Handler pattern Castor HTTP uses a small server-side handler abstraction inspired by Go's `net/http`. A handler receives a context, a request, and a response writer, then emits a response. ```php <?php namespace Castor\Net\Http; use Castor\Context; interface Handler { public function handle(Context $ctx, Request $request, ResponseWriter $writer): void; } ``` The package does not include a full web server. Server adapters, CGI bridges, middleware, and application routers can all target this interface. ## Implementing a handler ```php <?php use Castor\Context; use Castor\Net\Http\Handler; use Castor\Net\Http\Request; use Castor\Net\Http\ResponseWriter; use Castor\Net\Http\Status\Code; final class HelloHandler implements Handler { public function handle(Context $ctx, Request $request, ResponseWriter $writer): void { $writer->headers()->set('Content-Type', 'text/plain'); $writer->writeHeaders(Code::OK); $writer->write('Hello, World!'); } } ``` A handler should set headers before calling `writeHeaders` or writing the response body. If you call `write` first, a writer may send default `200 OK` headers automatically. ## Callback handlers Use `Castor\Net\Http\Handler\Callback` when a full class would be unnecessary: ```php <?php use Castor\Context; use Castor\Net\Http\Handler\Callback; use Castor\Net\Http\Request; use Castor\Net\Http\ResponseWriter; use Castor\Net\Http\Status\Code; $handler = Callback::fromCallable( function (Context $ctx, Request $request, ResponseWriter $writer): void { if ($request->method->value !== 'GET') { $writer->writeHeaders(Code::MethodNotAllowed); return; } $writer->headers()->set('Content-Type', 'text/plain'); $writer->write('ok'); // implicitly writes 200 OK on Recorder and similar writers } ); ``` `Callback::fromCallable` wraps any callable with the expected `(Context, Request, ResponseWriter): void` shape. ## Context carries operational data The request object contains only raw HTTP message data. Operational metadata belongs in `Castor\Context`. `RequestInfo` provides standard keys for common server-derived values: ```php <?php use Castor\Context; use Castor\Net\Http\RequestInfo; $ctx = Context::empty(); $ctx = RequestInfo::withClientIp($ctx, '203.0.113.10'); $ip = RequestInfo::clientIp($ctx); // "203.0.113.10" ``` Available helpers: | Helper | Purpose | | --- | --- | | `withParsedCookies` / `parsedCookies` | Store and retrieve parsed `Cookies`. Missing value returns an empty collection. | | `withClientIp` / `clientIp` | Store and retrieve the client IP. Missing value returns `0.0.0.0`. | | `withParsedBody` / `parsedBody` | Store and retrieve an already parsed request body. Missing value returns `null`. | This keeps request parsing and transport metadata out of the immutable `Request` value. ## Handler errors `Handler::handle` may throw `HandlerError` when the request cannot be handled. `Callback` automatically wraps thrown `Error` and `Exception` instances in `HandlerError` with an internal-server-error status code. ```php use Castor\Net\Http\HandlerError; use Castor\Net\Http\Status\Code; throw HandlerError::fromStatus(Code::BadRequest->value, 'Invalid payload'); ``` Server adapters can catch `HandlerError`, inspect its code, and translate it into an HTTP response. ## Testing handlers Use `ResponseWriter\Recorder` to exercise a handler without a network server: ```php <?php use Castor\Context; use Castor\Net\Http\Request; use Castor\Net\Http\ResponseWriter\Recorder; $writer = Recorder::create(); $handler->handle(Context::empty(), Request::create('GET', 'https://example.com'), $writer); $response = $writer->response(); ``` The recorder implements the same `ResponseWriter` interface and can produce a `Response` after headers have been written.
Castor ecosystem