{"id":37265139,"url":"https://github.com/guide42/ochenta","last_synced_at":"2026-01-16T00:05:39.350Z","repository":{"id":62513288,"uuid":"63996105","full_name":"guide42/ochenta","owner":"guide42","description":"HTTP library","archived":false,"fork":false,"pushed_at":"2020-06-21T14:50:39.000Z","size":220,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-18T22:16:00.319Z","etag":null,"topics":["http","middleware","request","responder","response"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/guide42.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-07-23T03:00:23.000Z","updated_at":"2020-06-21T14:50:42.000Z","dependencies_parsed_at":"2022-11-02T10:16:56.706Z","dependency_job_id":null,"html_url":"https://github.com/guide42/ochenta","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/guide42/ochenta","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guide42%2Fochenta","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guide42%2Fochenta/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guide42%2Fochenta/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guide42%2Fochenta/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/guide42","download_url":"https://codeload.github.com/guide42/ochenta/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guide42%2Fochenta/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28420814,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T10:47:48.104Z","status":"ssl_error","status_checked_at":"2026-01-14T10:46:19.031Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http","middleware","request","responder","response"],"created_at":"2026-01-16T00:05:38.607Z","updated_at":"2026-01-16T00:05:39.333Z","avatar_url":"https://github.com/guide42.png","language":"PHP","readme":"Ochenta: HTTP library\n=====================\n\n- HTTP abstractions: use request/response objects instead of superglobals.\n- HTTP middlewares: intersect the process of creating a response from a request.\n- HTTP responders: actionable views that build responses.\n\nHello World\n-----------\n\n```php\nuse ochenta\\ServerRequest;\nuse function ochenta\\{emit, responder_of};\n\nemit(new ServerRequest, responder_of('Hello World'));\n```\n\nInterested? Keep reading.\n\nUsage\n-----\n\n```php\n$req = new ServerRequest;\n```\n\nIt could also be created with it's defaults values:\n\n```php\n$req = new ServerRequest($_SERVER, $_GET, $_POST, $_FILES, $_COOKIE, fopen('php://input', 'rb'));\n```\n\nThat's a incoming request.  \nSuperglobals are available as `$req-\u003egetQuery()`, `$req-\u003egetParsedBody()` and `$req-\u003egetFiles()`.\n\nThen the low-level `Request` abstraction provides many more methods:\n\n- `$req-\u003egetMethod()` and `$req-\u003egetTarget()` from the request line.\n- `$req-\u003egetHeaders()` to get all headers and `$req-\u003egetHost()` for the normalized domain.\n- `$req-\u003egetMediaType()` and `$req-\u003egetCharset()` from the `Content-Type` header.\n- `$req-\u003egetAccept*()` returns the parsed Accept* headers.\n\nThere is also `Response` object, but the response will be given by a responder.\n\nResponders\n----------\n\nWhen working in SAPI environment, you could define a response with a responder:\n\n```php\nfunction hola(ServerRequest $req, callable $open) {\n    $name = $req-\u003egetQuery()['name'] ?? 'World';\n    $open(200, ['Content-Language' =\u003e ['en', 'es']]);\n    yield \"Hola $name\";\n}\n```\n\nOr using `ochenta\\Response` wrapper:\n\n```php\nfunction hola(ServerRequest $req, callable $open) {\n    $name = $req-\u003egetQuery()['name'] ?? 'World';\n    $res = new Response(200, ['Content-Language' =\u003e ['en', 'es']], \"Hola $name\");\n    return responder_of($response)($req, $open);\n}\n```\n\nUsing a `ochenta\\emit` function, the responder could be emitted:\n\n```php\nemit(new ServerRequest, @hola);\n```\n\nMiddlewares\n-----------\n\nUse them to wrap your `request -\u003e responder` process. This is what it look like:\n\n```php\nfunction timeit(callable $handler): callable {\n    return function(ServerRequest $req, callable $open) use($handler) {\n        $time = -microtime(TRUE);\n        $res = yield from $handler($req, $open);\n        $time += microtime(TRUE);\n        yield sprintf(\"\u003caddress\u003e%.7F secs\u003c/address\u003e\", $time);\n        return $res;\n    };\n}\n```\n\nDecorating your app responder:\n\n```php\n$app = @hola;\n$app = timeit($app);\n\nemit(new ServerRequest, $app);\n```\n\nWhen options are needed, could be wrapped in yet another function.\n\n```php\nfunction add_header(string $name, string $value): callable {\n    return function(callable $handler) use($name, $value): callable {\n        return function(ServerRequest $req, callable $open) use($name, $value, $handler) {\n            return $handler($req, function(int $status, array $headers) use($name, $value, $open) {\n                $headers[$name] = [$value];\n                $open($status, $headers);\n            });\n        };\n    };\n}\n```\n\nComplex? This middleware exists at `ochenta\\header`. This is how to use it:\n\n```php\n$app = add_header('X-Frame-Options', 'SAMEORIGIN')($app);\n```\n\nWhat a hassle! Better use `ochenta\\stack` to do stacks of middlewares:\n\n```php\n$app = stack(@hola, [\n    add_header('X-Xss-Protection', '1; mode=block'),\n    add_header('X-Frame-Options', 'SAMEORIGIN'),\n    @timeit,\n]);\n```\n\nYou got this far. Look at [example.php](example.php) to see the complete code.\n\nAPI\n---\n\n```php\nresponder_of(Response $resource)                             // creates a responder from a Response\nresponder_of(resource $resource)                             // ... from a resource\nresponder_of(scalar $resource)                               // ... from content\n\nemit(ServerRequest $req, callable $handler)                  // emits a responder\n\nstack(callable $responder, array $stack)                     // expects stack items to be a function(callable $next)\nstack(callable $responder, callable $resolver, array $stack) // ... use resolver as function(callable $prev, $handler)\n\n// MIDDLEWARES\n\nheader(string $name, array $values)                          // adds a header to responder\nheader(string $name, string $value)                          // ... with single value\n\ncookie(Cookie $cookie)                                       // sets cookie into responder\n\nappend(string $content)                                      // adds content before body\nappend(string $content, string $tag)                         // ... before every given tag\n\n// RESPONDERS\n\nredirect(string $uri)                                        // redirect to the given url\nredirect(string $uri, int $statusCode)                       // ... with given status code\n\n// CONTENT NEGOTATION\n\naccept\\mediatypes(Request $req, array $available)            // negotiate media types\naccept\\charsets(Request $req, array $available)              // ... charsets\naccept\\encodings(Request $req, array $available)             // ... encodings\naccept\\languages(Request $req, array $available)             // ... languages\n\n// HELPERS\n\nstream_of(scalar $resource)                                  // creates tmp file with $resouce content\n```\n\nBadges\n------\n\n[![Latest Stable Version](https://poser.pugx.org/guide42/ochenta/v/stable.svg)](https://packagist.org/packages/guide42/ochenta)\n[![Build Status](https://travis-ci.org/guide42/ochenta.svg?branch=master)](https://travis-ci.org/guide42/ochenta)\n[![Coverage Status](https://coveralls.io/repos/github/guide42/ochenta/badge.svg)](https://coveralls.io/github/guide42/ochenta)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguide42%2Fochenta","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fguide42%2Fochenta","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguide42%2Fochenta/lists"}