guide42/ochenta

HTTP request/response implementation

v0.6.0 2020-06-09 14:05 UTC

This package is auto-updated.

Last update: 2024-11-22 00:46:40 UTC


README

  • HTTP abstractions: use request/response objects instead of superglobals.
  • HTTP middlewares: intersect the process of creating a response from a request.
  • HTTP responders: actionable views that build responses.

Hello World

use ochenta\ServerRequest;
use function ochenta\{emit, responder_of};

emit(new ServerRequest, responder_of('Hello World'));

Interested? Keep reading.

Usage

$req = new ServerRequest;

It could also be created with it's defaults values:

$req = new ServerRequest($_SERVER, $_GET, $_POST, $_FILES, $_COOKIE, fopen('php://input', 'rb'));

That's a incoming request.
Superglobals are available as $req->getQuery(), $req->getParsedBody() and $req->getFiles().

Then the low-level Request abstraction provides many more methods:

  • $req->getMethod() and $req->getTarget() from the request line.
  • $req->getHeaders() to get all headers and $req->getHost() for the normalized domain.
  • $req->getMediaType() and $req->getCharset() from the Content-Type header.
  • $req->getAccept*() returns the parsed Accept* headers.

There is also Response object, but the response will be given by a responder.

Responders

When working in SAPI environment, you could define a response with a responder:

function hola(ServerRequest $req, callable $open) {
    $name = $req->getQuery()['name'] ?? 'World';
    $open(200, ['Content-Language' => ['en', 'es']]);
    yield "Hola $name";
}

Or using ochenta\Response wrapper:

function hola(ServerRequest $req, callable $open) {
    $name = $req->getQuery()['name'] ?? 'World';
    $res = new Response(200, ['Content-Language' => ['en', 'es']], "Hola $name");
    return responder_of($response)($req, $open);
}

Using a ochenta\emit function, the responder could be emitted:

emit(new ServerRequest, @hola);

Middlewares

Use them to wrap your request -> responder process. This is what it look like:

function timeit(callable $handler): callable {
    return function(ServerRequest $req, callable $open) use($handler) {
        $time = -microtime(TRUE);
        $res = yield from $handler($req, $open);
        $time += microtime(TRUE);
        yield sprintf("<address>%.7F secs</address>", $time);
        return $res;
    };
}

Decorating your app responder:

$app = @hola;
$app = timeit($app);

emit(new ServerRequest, $app);

When options are needed, could be wrapped in yet another function.

function add_header(string $name, string $value): callable {
    return function(callable $handler) use($name, $value): callable {
        return function(ServerRequest $req, callable $open) use($name, $value, $handler) {
            return $handler($req, function(int $status, array $headers) use($name, $value, $open) {
                $headers[$name] = [$value];
                $open($status, $headers);
            });
        };
    };
}

Complex? This middleware exists at ochenta\header. This is how to use it:

$app = add_header('X-Frame-Options', 'SAMEORIGIN')($app);

What a hassle! Better use ochenta\stack to do stacks of middlewares:

$app = stack(@hola, [
    add_header('X-Xss-Protection', '1; mode=block'),
    add_header('X-Frame-Options', 'SAMEORIGIN'),
    @timeit,
]);

You got this far. Look at example.php to see the complete code.

API

responder_of(Response $resource)                             // creates a responder from a Response
responder_of(resource $resource)                             // ... from a resource
responder_of(scalar $resource)                               // ... from content

emit(ServerRequest $req, callable $handler)                  // emits a responder

stack(callable $responder, array $stack)                     // expects stack items to be a function(callable $next)
stack(callable $responder, callable $resolver, array $stack) // ... use resolver as function(callable $prev, $handler)

// MIDDLEWARES

header(string $name, array $values)                          // adds a header to responder
header(string $name, string $value)                          // ... with single value

cookie(Cookie $cookie)                                       // sets cookie into responder

append(string $content)                                      // adds content before body
append(string $content, string $tag)                         // ... before every given tag

// RESPONDERS

redirect(string $uri)                                        // redirect to the given url
redirect(string $uri, int $statusCode)                       // ... with given status code

// CONTENT NEGOTATION

accept\mediatypes(Request $req, array $available)            // negotiate media types
accept\charsets(Request $req, array $available)              // ... charsets
accept\encodings(Request $req, array $available)             // ... encodings
accept\languages(Request $req, array $available)             // ... languages

// HELPERS

stream_of(scalar $resource)                                  // creates tmp file with $resouce content

Badges

Latest Stable Version Build Status Coverage Status