From de13d9da7594a3afd14651c320ae767d7c404736 Mon Sep 17 00:00:00 2001 From: eater Date: Wed, 17 Jan 2018 12:54:06 +0100 Subject: [PATCH] Initial commit --- .gitignore | 2 + composer.json | 14 ++ composer.lock | 399 +++++++++++++++++++++++++++++++++++++++++++++ config/default.php | 24 +++ src/Handler.php | 30 ++++ src/Server.php | 159 ++++++++++++++++++ 6 files changed, 628 insertions(+) create mode 100644 .gitignore create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 config/default.php create mode 100644 src/Handler.php create mode 100644 src/Server.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0dca145 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +.idea diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..4586b24 --- /dev/null +++ b/composer.json @@ -0,0 +1,14 @@ +{ + "name": "bitcommunism/http", + "version": "1.0.1", + "require": { + "nikic/fast-route": "^1.2", + "guzzlehttp/psr7": "^1.4", + "php-di/php-di": "^5.4" + }, + "autoload": { + "psr-4": { + "BitCommunism\\Http\\": "src/" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..82d0091 --- /dev/null +++ b/composer.lock @@ -0,0 +1,399 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "6bae8a2fc61864087dc8d4f14e7cd6eb", + "packages": [ + { + "name": "container-interop/container-interop", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/container-interop/container-interop.git", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "shasum": "" + }, + "require": { + "psr/container": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "homepage": "https://github.com/container-interop/container-interop", + "time": "2017-02-14T19:40:03+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2017-03-20T17:10:46+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "b5f95749071c82a8e0f58586987627054400cdf6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/b5f95749071c82a8e0f58586987627054400cdf6", + "reference": "b5f95749071c82a8e0f58586987627054400cdf6", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "FastRoute\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "time": "2017-01-19T11:35:12+00:00" + }, + { + "name": "php-di/invoker", + "version": "1.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "1f4ca63b9abc66109e53b255e465d0ddb5c2e3f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/1f4ca63b9abc66109e53b255e465d0ddb5c2e3f7", + "reference": "1f4ca63b9abc66109e53b255e465d0ddb5c2e3f7", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "~1.1" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "phpunit/phpunit": "~4.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "time": "2016-07-14T13:09:58+00:00" + }, + { + "name": "php-di/php-di", + "version": "5.4.6", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "3f9255659595f3e289f473778bb6c51aa72abbbd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/3f9255659595f3e289f473778bb6c51aa72abbbd", + "reference": "3f9255659595f3e289f473778bb6c51aa72abbbd", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "~1.2", + "php": ">=5.5.0", + "php-di/invoker": "^1.3.2", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "~1.0" + }, + "provide": { + "container-interop/container-interop-implementation": "^1.0", + "psr/container-implementation": "^1.0" + }, + "replace": { + "mnapoli/php-di": "*" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "doctrine/cache": "~1.4", + "mnapoli/phpunit-easymock": "~0.2.0", + "ocramius/proxy-manager": "~1.0|~2.0", + "phpbench/phpbench": "@dev", + "phpunit/phpunit": "~4.5" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "doctrine/cache": "Install it if you want to use the cache (version ~1.4)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~1.0 or ~2.0)" + }, + "type": "library", + "autoload": { + "psr-4": { + "DI\\": "src/DI/" + }, + "files": [ + "src/DI/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "http://php-di.org/", + "keywords": [ + "container", + "dependency injection", + "di" + ], + "time": "2017-12-03T08:20:27+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "83f5ead159defccfa8e7092e5b6c1c533b326d68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/83f5ead159defccfa8e7092e5b6c1c533b326d68", + "reference": "83f5ead159defccfa8e7092e5b6c1c533b326d68", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "time": "2015-11-29T10:34:25+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/config/default.php b/config/default.php new file mode 100644 index 0000000..9d4eb66 --- /dev/null +++ b/config/default.php @@ -0,0 +1,24 @@ + add([ + 'http' => function(Server $server) { + $server->serve(); + } + ]), + + 'marx.loaders' => add([ + function(Server $server) { + $server->buildRoutes(); + } + ]), + + Server::class => object()->constructorParameter('routes', get('routes')), +]; \ No newline at end of file diff --git a/src/Handler.php b/src/Handler.php new file mode 100644 index 0000000..cd596da --- /dev/null +++ b/src/Handler.php @@ -0,0 +1,30 @@ +container = $container; + } + + protected function get($name, $default = null) { + if (!$this->container->has($name)) { + return $default; + } + + return $this->container->get($name); + } +} \ No newline at end of file diff --git a/src/Server.php b/src/Server.php new file mode 100644 index 0000000..b1753d5 --- /dev/null +++ b/src/Server.php @@ -0,0 +1,159 @@ +routes = $routes; + $this->container = $container; + } + + private function iterateRoutes() + { + yield from $this->iterateRoutesFromArray($this->routes); + } + + private function iterateRoutesFromArray($routeArr, $prefix = '', $known = [], $middleware = []) + { + foreach ($routeArr as $key => $route) { + if ($key === 'middleware') { + $middleware = array_merge($middleware, $route); + } + + if (is_array($route) && !(isset($route[0]) && isset($route[1]))) { + yield from $this->iterateRoutesFromArray($route, $prefix . $key, $known); + continue; + } + + if (is_string($route)) { + $route = [$route, 'handle', []]; + } + + $route[2] = array_merge($middleware, $route[2] ?? []); + + if ($key[0] !== '/') { + yield [$key, $prefix, $route]; + + continue; + } + + yield ['GET', $prefix . $key, $route]; + } + } + + + public function buildRoutes() + { + $this->router = simpleDispatcher(function (RouteCollector $collector) { + foreach ($this->iterateRoutes() as $route) { + $collector->addRoute(...$route); + } + }); + } + + public function serve() + { + $request = ServerRequest::fromGlobals(); + $path = $request->getUri()->getPath(); + $method = $request->getMethod(); + $routeInfo = $this->router->dispatch($method, $path); + + $response = null; + + switch ($routeInfo[0]) { + case Dispatcher::NOT_FOUND: + // todo: Error page + break; + case Dispatcher::METHOD_NOT_ALLOWED: + // todo: Error page + break; + case Dispatcher::FOUND: + [$_, $handler, $vars] = $routeInfo; + $response = $this->handleHttp($request, $handler, $vars); + break; + } + + if ($response === null) { + // todo: Error page + } + + header('Status: ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase()); + + $wrote = []; + foreach ($response->getHeaders() as [$headerName, $headerValue]) { + // Only replace header if we didn't write on those yet + header($headerName . ': ' . $headerValue, !isset($wrote[$headerName])); + $wrote[$headerName] = true; + } + + $stdout = fopen('php://output', 'w+'); + + copy_to_stream($response->getBody(), stream_for($stdout)); + } + + public function handleHttp(ServerRequestInterface $request, array $handler, array $vars): ResponseInterface + { + [$_, $_, $middlewareArray] = $handler; + + $middlewarePointer = 0; + + $next = null; + $response = new Response(200, [], stream_for('No answer')); + + $next = function ($request, $response, $vars) use ($handler, $middlewareArray, &$middlewarePointer, &$next) { + $output = null; + + if (count($middlewareArray) >= $middlewarePointer) { + $obj = $this->container->get($handler[0]); + $output = $obj->{$handler[1]}($request, $response, $vars); + } else { + + [$name, $func] = $middlewareArray[$middlewarePointer++]; + + $middleware = $this->container->get($name); + $output = $middleware->{$func}($request, $response, $vars); + } + + return $this->normalizeResponse($output, $response); + }; + + return $next($request, $response, $vars); + } + + private function normalizeResponse($response, ResponseInterface $previousResponse) { + if ($response instanceof ResponseInterface) { + return $response; + } + + if (!$response) { + return $previousResponse; + } + + $body = stream_for($response); + return $previousResponse->withBody($body); + } +} \ No newline at end of file