Initialize Nest HTTP library.
This commit is contained in:
commit
35418919ed
26 changed files with 1896 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
# IDEA
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
# Composer
|
||||
vendor/
|
37
composer.json
Normal file
37
composer.json
Normal file
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"version": "1.0",
|
||||
"name": "nest/http",
|
||||
"description": "Nest HTTP service and engine.",
|
||||
"type": "library",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Madeorsk",
|
||||
"email": "madeorsk@protonmail.com"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Nest\\Http\\": "src/"
|
||||
}
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://code.zeptotech.net/Nest/Core"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.3",
|
||||
"nest/core": "dev-main",
|
||||
"nyholm/psr7": "^1.8",
|
||||
"ralouphie/getallheaders": "^3.0",
|
||||
"psr/http-server-handler": "^1.0",
|
||||
"psr/http-server-middleware": "^1.0",
|
||||
"psr/http-message": "^2.0",
|
||||
"psr/http-factory": "^1.1",
|
||||
"filp/whoops": "^2.16"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-dom": "*"
|
||||
}
|
||||
}
|
520
composer.lock
generated
Normal file
520
composer.lock
generated
Normal file
|
@ -0,0 +1,520 @@
|
|||
{
|
||||
"_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#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "2ddbf7190a614a3bab8ab92fd7179bbc",
|
||||
"packages": [
|
||||
{
|
||||
"name": "filp/whoops",
|
||||
"version": "2.16.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filp/whoops.git",
|
||||
"reference": "befcdc0e5dce67252aa6322d82424be928214fa2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filp/whoops/zipball/befcdc0e5dce67252aa6322d82424be928214fa2",
|
||||
"reference": "befcdc0e5dce67252aa6322d82424be928214fa2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1 || ^8.0",
|
||||
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.0",
|
||||
"phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3",
|
||||
"symfony/var-dumper": "^4.0 || ^5.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
|
||||
"whoops/soap": "Formats errors as SOAP responses"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.7-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Whoops\\": "src/Whoops/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Filipe Dobreira",
|
||||
"homepage": "https://github.com/filp",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "php error handling for cool kids",
|
||||
"homepage": "https://filp.github.io/whoops/",
|
||||
"keywords": [
|
||||
"error",
|
||||
"exception",
|
||||
"handling",
|
||||
"library",
|
||||
"throwable",
|
||||
"whoops"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/filp/whoops/issues",
|
||||
"source": "https://github.com/filp/whoops/tree/2.16.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/denis-sokolov",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-25T12:00:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nest/core",
|
||||
"version": "dev-main",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://code.zeptotech.net/Nest/Core",
|
||||
"reference": "22296b8d7c8b528089189a8d9500395424cb6468"
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.3"
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Nest\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/Utils/Array.php",
|
||||
"src/Utils/Paths.php",
|
||||
"src/Utils/Reflection.php",
|
||||
"src/Utils/String.php"
|
||||
]
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Madeorsk",
|
||||
"email": "madeorsk@protonmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Nest framework core.",
|
||||
"time": "2024-11-08T11:12:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nyholm/psr7",
|
||||
"version": "1.8.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Nyholm/psr7.git",
|
||||
"reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3",
|
||||
"reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/http-message": "^1.1 || ^2.0"
|
||||
},
|
||||
"provide": {
|
||||
"php-http/message-factory-implementation": "1.0",
|
||||
"psr/http-factory-implementation": "1.0",
|
||||
"psr/http-message-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"http-interop/http-factory-tests": "^0.9",
|
||||
"php-http/message-factory": "^1.0",
|
||||
"php-http/psr7-integration-tests": "^1.0",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
|
||||
"symfony/error-handler": "^4.4"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.8-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Nyholm\\Psr7\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Martijn van der Ven",
|
||||
"email": "martijn@vanderven.se"
|
||||
}
|
||||
],
|
||||
"description": "A fast PHP7 implementation of PSR-7",
|
||||
"homepage": "https://tnyholm.se",
|
||||
"keywords": [
|
||||
"psr-17",
|
||||
"psr-7"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Nyholm/psr7/issues",
|
||||
"source": "https://github.com/Nyholm/psr7/tree/1.8.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/Zegnat",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nyholm",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-09T07:06:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-factory",
|
||||
"version": "1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-factory.git",
|
||||
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
|
||||
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1",
|
||||
"psr/http-message": "^1.0 || ^2.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": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
|
||||
"keywords": [
|
||||
"factory",
|
||||
"http",
|
||||
"message",
|
||||
"psr",
|
||||
"psr-17",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-factory"
|
||||
},
|
||||
"time": "2024-04-15T12:06:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-message",
|
||||
"version": "2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-message.git",
|
||||
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
|
||||
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Message\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://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"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-message/tree/2.0"
|
||||
},
|
||||
"time": "2023-04-04T09:54:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-server-handler",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-server-handler.git",
|
||||
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4",
|
||||
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.0",
|
||||
"psr/http-message": "^1.0 || ^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Server\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP server-side request handler",
|
||||
"keywords": [
|
||||
"handler",
|
||||
"http",
|
||||
"http-interop",
|
||||
"psr",
|
||||
"psr-15",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response",
|
||||
"server"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-server-handler/tree/1.0.2"
|
||||
},
|
||||
"time": "2023-04-10T20:06:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-server-middleware",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-server-middleware.git",
|
||||
"reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829",
|
||||
"reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.0",
|
||||
"psr/http-message": "^1.0 || ^2.0",
|
||||
"psr/http-server-handler": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Server\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP server-side middleware",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-interop",
|
||||
"middleware",
|
||||
"psr",
|
||||
"psr-15",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-fig/http-server-middleware/issues",
|
||||
"source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2"
|
||||
},
|
||||
"time": "2023-04-11T06:14:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/log",
|
||||
"version": "3.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/log.git",
|
||||
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
|
||||
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Log\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for logging libraries",
|
||||
"homepage": "https://github.com/php-fig/log",
|
||||
"keywords": [
|
||||
"log",
|
||||
"psr",
|
||||
"psr-3"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/log/tree/3.0.2"
|
||||
},
|
||||
"time": "2024-09-11T13:17:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ralouphie/getallheaders",
|
||||
"version": "3.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ralouphie/getallheaders.git",
|
||||
"reference": "120b605dfeb996808c31b6477290a714d356e822"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
|
||||
"reference": "120b605dfeb996808c31b6477290a714d356e822",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-coveralls/php-coveralls": "^2.1",
|
||||
"phpunit/phpunit": "^5 || ^6.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/getallheaders.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ralph Khattar",
|
||||
"email": "ralph.khattar@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A polyfill for getallheaders.",
|
||||
"support": {
|
||||
"issues": "https://github.com/ralouphie/getallheaders/issues",
|
||||
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
|
||||
},
|
||||
"time": "2019-03-08T08:55:37+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {
|
||||
"nest/core": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": "^8.3"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
13
src/ContentType.php
Normal file
13
src/ContentType.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http;
|
||||
|
||||
/**
|
||||
* Typical content types.
|
||||
* A generic function shouldn't use it as an enforced parameters as content types can be fully customized.
|
||||
*/
|
||||
enum ContentType: string
|
||||
{
|
||||
case Json = "application/json";
|
||||
case Text = "plain/text";
|
||||
}
|
63
src/ErrorsMiddleware.php
Normal file
63
src/ErrorsMiddleware.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http;
|
||||
|
||||
use Nest\Http\Exceptions\HttpException;
|
||||
use Nest\Http\Exceptions\InternalErrorException;
|
||||
use Nyholm\Psr7\Response;
|
||||
use Override;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Throwable;
|
||||
use Whoops\Handler\PrettyPageHandler;
|
||||
use Whoops\Run as Whoops;
|
||||
|
||||
/**
|
||||
* Default errors handler middleware.
|
||||
*/
|
||||
class ErrorsMiddleware implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @param HttpConfiguration $configuration HTTP service configuration instance.
|
||||
*/
|
||||
public function __construct(protected HttpConfiguration $configuration)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
#[Override] public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
try
|
||||
{
|
||||
return $handler->handle($request);
|
||||
}
|
||||
catch (Throwable $exception)
|
||||
{ // Handle the exception.
|
||||
if (!($exception instanceof HttpException))
|
||||
{
|
||||
if ($this->configuration->getDebug())
|
||||
{ // In debug mode, show details about the thrown exception.
|
||||
$whoops = new Whoops();
|
||||
$whoops->writeToOutput(false);
|
||||
$whoops->allowQuit(false);
|
||||
$whoops->pushHandler(new PrettyPageHandler());
|
||||
$body = $whoops->handleException($exception);
|
||||
|
||||
// Something bad happened, return an internal error response.
|
||||
return new Response(500, body: $body);
|
||||
}
|
||||
|
||||
// Otherwise, show an internal error exception.
|
||||
$exception = new InternalErrorException(previous: $exception);
|
||||
}
|
||||
|
||||
// Should always be an HTTP exception at this point.
|
||||
$renderer = new ($this->configuration->getExceptionRenderer())($exception);
|
||||
return new Response($renderer->getStatusCode(), body: $renderer->getBody());
|
||||
}
|
||||
}
|
||||
}
|
39
src/ExceptionRenderer.php
Normal file
39
src/ExceptionRenderer.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http;
|
||||
|
||||
use Nest\Http\Exceptions\HttpException;
|
||||
use Nest\Http\Renderer\PhpRenderer;
|
||||
|
||||
/**
|
||||
* Render an HTTP exception.
|
||||
*/
|
||||
class ExceptionRenderer
|
||||
{
|
||||
/**
|
||||
* @param HttpException $exception The exception to render.
|
||||
*/
|
||||
public function __construct(protected HttpException $exception)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Return the status code for the exception.
|
||||
* @return int Exception status code.
|
||||
*/
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
return $this->exception->getStatusCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the body of the HTTP exception.
|
||||
* @return string HTTP exception body.
|
||||
*/
|
||||
public function getBody(): string
|
||||
{
|
||||
// Render the default exception renderer.
|
||||
return (new PhpRenderer(__DIR__."/views/exception.php"))->render((object) [
|
||||
"exception" => $this->exception,
|
||||
]);
|
||||
}
|
||||
}
|
19
src/Exceptions/ClientException.php
Normal file
19
src/Exceptions/ClientException.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http\Exceptions;
|
||||
|
||||
use Nest\Http\StatusCode;
|
||||
|
||||
/**
|
||||
* HTTP client error exception.
|
||||
*/
|
||||
class ClientException extends HttpException
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
#[\Override] public function getStatusCode(): int
|
||||
{
|
||||
return StatusCode::BadRequest->value;
|
||||
}
|
||||
}
|
17
src/Exceptions/HttpException.php
Normal file
17
src/Exceptions/HttpException.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http\Exceptions;
|
||||
|
||||
use Nest\Exceptions\Exception;
|
||||
|
||||
/**
|
||||
* An HTTP exception.
|
||||
*/
|
||||
abstract class HttpException extends Exception
|
||||
{
|
||||
/**
|
||||
* Get the HTTP status code to use when this exception is thrown.
|
||||
* @return int Status code to return.
|
||||
*/
|
||||
public abstract function getStatusCode(): int;
|
||||
}
|
16
src/Exceptions/InternalErrorException.php
Normal file
16
src/Exceptions/InternalErrorException.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http\Exceptions;
|
||||
|
||||
use Nest\Http\StatusCode;
|
||||
|
||||
/**
|
||||
* HTTP internal error exception.
|
||||
*/
|
||||
class InternalErrorException extends ClientException
|
||||
{
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
return StatusCode::InternalServerError->value;
|
||||
}
|
||||
}
|
16
src/Exceptions/NotFoundException.php
Normal file
16
src/Exceptions/NotFoundException.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http\Exceptions;
|
||||
|
||||
use Nest\Http\StatusCode;
|
||||
|
||||
/**
|
||||
* HTTP not found exception.
|
||||
*/
|
||||
class NotFoundException extends ClientException
|
||||
{
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
return StatusCode::NotFound->value;
|
||||
}
|
||||
}
|
19
src/Exceptions/ServerException.php
Normal file
19
src/Exceptions/ServerException.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http\Exceptions;
|
||||
|
||||
use Nest\Http\StatusCode;
|
||||
|
||||
/**
|
||||
* HTTP server error exception.
|
||||
*/
|
||||
class ServerException extends HttpException
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
#[\Override] public function getStatusCode(): int
|
||||
{
|
||||
return StatusCode::InternalServerError->value;
|
||||
}
|
||||
}
|
13
src/Header.php
Normal file
13
src/Header.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http;
|
||||
|
||||
/**
|
||||
* Typical headers.
|
||||
* A generic function shouldn't use it as an enforced parameters as headers can be fully customized.
|
||||
*/
|
||||
enum Header: string
|
||||
{
|
||||
case Authorization = "Authorization";
|
||||
case ContentType = "ContentType";
|
||||
}
|
132
src/Http.php
Normal file
132
src/Http.php
Normal file
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http;
|
||||
|
||||
use Nest\Application;
|
||||
use Nyholm\Psr7\ServerRequest;
|
||||
use Override;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
/**
|
||||
* HTTP service manager.
|
||||
*/
|
||||
class Http
|
||||
{
|
||||
/**
|
||||
* Current request instance.
|
||||
* @var ServerRequestInterface
|
||||
*/
|
||||
private ServerRequestInterface $request;
|
||||
|
||||
/**
|
||||
* @param Application $application The application.
|
||||
* @param HttpConfiguration $configuration HTTP service configuration.
|
||||
*/
|
||||
public function __construct(protected Application $application, protected HttpConfiguration $configuration)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTTP debug status.
|
||||
* @return bool HTTP debug status.
|
||||
*/
|
||||
public function getDebug(): bool
|
||||
{
|
||||
return $this->configuration->getDebug();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the current server request from global context.
|
||||
* @return ServerRequestInterface Parsed server request.
|
||||
*/
|
||||
protected function parseRequest(): ServerRequestInterface
|
||||
{
|
||||
return $this->request = new ServerRequest(
|
||||
$_SERVER["REQUEST_METHOD"],
|
||||
$_SERVER["REQUEST_URI"],
|
||||
getallheaders(),
|
||||
fopen("php://input", "r"),
|
||||
"1.1",
|
||||
$_SERVER,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a chain of request handlers (with middlewares).
|
||||
* @return RequestHandlerInterface
|
||||
*/
|
||||
protected function buildCallChain(): RequestHandlerInterface
|
||||
{
|
||||
// Set the final request handler as the current one.
|
||||
$currentHandler = $this->configuration->getRequestHandler();
|
||||
|
||||
foreach ($this->configuration->getMiddlewares() as $middleware)
|
||||
{ // Add each middleware.
|
||||
// The current handler is calling the middleware which will call the previously current one if needed.
|
||||
$currentHandler = new class($middleware, $currentHandler) implements RequestHandlerInterface {
|
||||
public function __construct(private MiddlewareInterface $middleware, private RequestHandlerInterface $requestHandler)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
#[Override] public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
return $this->middleware->process($request, $this->requestHandler);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Return the built request handler.
|
||||
return $currentHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start HTTP engine.
|
||||
* @return void
|
||||
*/
|
||||
public function __invoke(): void
|
||||
{
|
||||
// Create and initialize request object.
|
||||
$this->parseRequest();
|
||||
|
||||
// Get response from handler and send it.
|
||||
$response = $this->buildCallChain()->handle($this->request);
|
||||
$this->output($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a given response.
|
||||
* @param ResponseInterface $response The response to output.
|
||||
* @return void
|
||||
*/
|
||||
public function output(ResponseInterface $response): void
|
||||
{
|
||||
// Send status code.
|
||||
http_response_code($response->getStatusCode());
|
||||
|
||||
// Send headers.
|
||||
foreach ($response->getHeaders() as $name => $values)
|
||||
{ // Send each header.
|
||||
foreach ($values as $value)
|
||||
{ // Send each header value.
|
||||
header("$name: $value", false);
|
||||
}
|
||||
}
|
||||
|
||||
// Send body.
|
||||
echo $response->getBody()->getContents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current request instance, if there is one.
|
||||
* @return ServerRequestInterface|null
|
||||
*/
|
||||
public function getRequest(): ?ServerRequestInterface
|
||||
{
|
||||
return $this->request ?? null;
|
||||
}
|
||||
}
|
191
src/HttpConfiguration.php
Normal file
191
src/HttpConfiguration.php
Normal file
|
@ -0,0 +1,191 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http;
|
||||
|
||||
use Nest\Exceptions\InvalidTypeException;
|
||||
use Nest\Services\ServiceConfiguration;
|
||||
use Override;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use function Nest\Utils\expect_type;
|
||||
|
||||
/**
|
||||
* HTTP configuration class.
|
||||
*/
|
||||
class HttpConfiguration extends ServiceConfiguration
|
||||
{
|
||||
/**
|
||||
* HTTP debug mode.
|
||||
* This is NOT suitable for production as it outputs clearly some code and may share secrets.
|
||||
* @var bool
|
||||
*/
|
||||
private bool $debug = false;
|
||||
|
||||
/**
|
||||
* HTTP exception renderer class.
|
||||
* @var string
|
||||
*/
|
||||
private string $exceptionRenderer = ExceptionRenderer::class;
|
||||
|
||||
/**
|
||||
* The main request handler.
|
||||
* @var RequestHandlerInterface
|
||||
*/
|
||||
private RequestHandlerInterface $requestHandler;
|
||||
|
||||
/**
|
||||
* List of global middlewares.
|
||||
* @var MiddlewareInterface[]
|
||||
*/
|
||||
private array $middlewares = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Add default middlewares.
|
||||
$this->addDefaultMiddlewares();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set debug mode.
|
||||
* @param bool $debug Debug mode status.
|
||||
* @return $this
|
||||
*/
|
||||
public function debug(bool $debug = true): static
|
||||
{
|
||||
$this->debug = $debug;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set exception renderer class.
|
||||
* @param string $exceptionRenderer The exception renderer to use and an HTTP exception is thrown.
|
||||
* @return $this
|
||||
* @throws InvalidTypeException
|
||||
*/
|
||||
public function exceptionRenderer(string $exceptionRenderer): static
|
||||
{
|
||||
expect_type($exceptionRenderer, ExceptionRenderer::class);
|
||||
$this->exceptionRenderer = $exceptionRenderer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set HTTP request handler.
|
||||
* @param (callable(ServerRequestInterface): ResponseInterface)|RequestHandlerInterface $requestHandler
|
||||
* @return $this
|
||||
*/
|
||||
public function requestHandler(callable|RequestHandlerInterface $requestHandler): static
|
||||
{
|
||||
if (!($requestHandler instanceof RequestHandlerInterface))
|
||||
// Convert a callable to a request handler interface.
|
||||
$requestHandler = new class($requestHandler) implements RequestHandlerInterface {
|
||||
private $requestHandler;
|
||||
|
||||
public function __construct(callable $requestHandler)
|
||||
{
|
||||
$this->requestHandler = $requestHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
#[Override] public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
return ($this->requestHandler)($request);
|
||||
}
|
||||
};
|
||||
|
||||
$this->requestHandler = $requestHandler;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset middlewares.
|
||||
* @return $this
|
||||
*/
|
||||
public function resetMiddlewares(): static
|
||||
{
|
||||
$this->middlewares = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add default middlewares.
|
||||
* They are added in the constructor, so if you didn't reset the middlewares, running this isn't required.
|
||||
* @return $this
|
||||
*/
|
||||
public function addDefaultMiddlewares(): static
|
||||
{
|
||||
$this->middlewares[] = new ErrorsMiddleware($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new middleware to the stack.
|
||||
* What is added first is called first in request handling.
|
||||
* @param (callable(ServerRequestInterface, RequestHandlerInterface): ResponseInterface)|MiddlewareInterface $middleware
|
||||
* @return $this
|
||||
*/
|
||||
public function addMiddleware(callable|MiddlewareInterface $middleware): static
|
||||
{
|
||||
if (!($middleware instanceof MiddlewareInterface))
|
||||
// Convert a callable to a middleware handler interface.
|
||||
$middleware = new class($middleware) implements MiddlewareInterface {
|
||||
private $middleware;
|
||||
|
||||
public function __construct(callable $middleware)
|
||||
{
|
||||
$this->middleware = $middleware;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
#[Override] public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
return ($this->middleware)($request, $handler);
|
||||
}
|
||||
};
|
||||
|
||||
$this->middlewares[] = $middleware;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTTP debug status.
|
||||
* @return bool HTTP debug status.
|
||||
*/
|
||||
public function getDebug(): bool
|
||||
{
|
||||
return $this->debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exception renderer instance.
|
||||
* @return string
|
||||
*/
|
||||
public function getExceptionRenderer(): string
|
||||
{
|
||||
return $this->exceptionRenderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configured request handler.
|
||||
* @return RequestHandlerInterface
|
||||
*/
|
||||
public function getRequestHandler(): RequestHandlerInterface
|
||||
{
|
||||
return $this->requestHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configured middlewares stack.
|
||||
* @return MiddlewareInterface[]
|
||||
*/
|
||||
public function getMiddlewares(): array
|
||||
{
|
||||
return $this->middlewares;
|
||||
}
|
||||
}
|
36
src/HttpService.php
Normal file
36
src/HttpService.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http;
|
||||
|
||||
use Nest\Exceptions\Services\Configuration\UndefinedServiceConfigurationException;
|
||||
|
||||
/**
|
||||
* HTTP Nest service.
|
||||
*/
|
||||
trait HttpService
|
||||
{
|
||||
/**
|
||||
* HTTP manager.
|
||||
* @var Http
|
||||
*/
|
||||
private Http $http;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws UndefinedServiceConfigurationException
|
||||
*/
|
||||
protected function __nest__HttpService(): void
|
||||
{
|
||||
// Initialize HTTP manager.
|
||||
$this->http = new Http($this, $this->getServiceConfiguration(HttpConfiguration::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTTP manager.
|
||||
* @return Http HTTP manager instance.
|
||||
*/
|
||||
public function http(): Http
|
||||
{
|
||||
return $this->http;
|
||||
}
|
||||
}
|
31
src/Renderer/PhpRenderer.php
Normal file
31
src/Renderer/PhpRenderer.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http\Renderer;
|
||||
|
||||
/**
|
||||
* Plain PHP body renderer.
|
||||
*/
|
||||
class PhpRenderer implements Renderer
|
||||
{
|
||||
/**
|
||||
* @param string $viewPath Path of the view to use when rendering.
|
||||
*/
|
||||
public function __construct(protected string $viewPath)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function render(object $arguments): string
|
||||
{
|
||||
// Extract render arguments.
|
||||
extract((array) $arguments);
|
||||
|
||||
// Start rendering while buffering the output.
|
||||
ob_start();
|
||||
include $this->viewPath;
|
||||
|
||||
// Return buffered body.
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
16
src/Renderer/Renderer.php
Normal file
16
src/Renderer/Renderer.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http\Renderer;
|
||||
|
||||
/**
|
||||
* Renderer interface.
|
||||
*/
|
||||
interface Renderer
|
||||
{
|
||||
/**
|
||||
* Render a view with the given arguments.
|
||||
* @param object $arguments View arguments.
|
||||
* @return string Produced view.
|
||||
*/
|
||||
function render(object $arguments): string;
|
||||
}
|
12
src/RequestAuthentication.php
Normal file
12
src/RequestAuthentication.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http;
|
||||
|
||||
/**
|
||||
* HTTP request authentication data.
|
||||
*/
|
||||
class RequestAuthentication
|
||||
{
|
||||
public function __construct(public ?string $type, public ?string $user, public ?string $password)
|
||||
{}
|
||||
}
|
105
src/Router/Controller.php
Normal file
105
src/Router/Controller.php
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http\Router;
|
||||
|
||||
use DOMDocument;
|
||||
use Nest\Application;
|
||||
use Nest\Http\ContentType;
|
||||
use Nest\Http\Header;
|
||||
use Nest\Http\Renderer\Renderer;
|
||||
use Nest\Http\StatusCode;
|
||||
use Nyholm\Psr7\Response;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Stringable;
|
||||
|
||||
/**
|
||||
* An abstract HTTP route controller.
|
||||
*/
|
||||
abstract class Controller
|
||||
{
|
||||
/**
|
||||
* @param Application $app The application.
|
||||
*/
|
||||
public function __construct(protected Application $app)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Get current request.
|
||||
* @return ServerRequestInterface The request instance.
|
||||
*/
|
||||
public function getRequest(): ServerRequestInterface
|
||||
{
|
||||
return $this->app->http()->getRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new response with the given status code and data.
|
||||
* @param StatusCode $statusCode The status code to respond.
|
||||
* @param string|null $contentType The content type of the response.
|
||||
* @param string|resource|StreamInterface|null $body The body to respond.
|
||||
* @return ResponseInterface The new response.
|
||||
*/
|
||||
public function newResponse(StatusCode $statusCode = StatusCode::OK, ?string $contentType = null, mixed $body = null): ResponseInterface
|
||||
{
|
||||
// Initialize headers.
|
||||
$headers = [];
|
||||
if (!empty($contentType)) $headers[Header::ContentType->value] = $contentType;
|
||||
|
||||
return new Response(
|
||||
// Pass the status code.
|
||||
status: $statusCode->value,
|
||||
// Pass headers.
|
||||
headers: $headers,
|
||||
// Pass the raw body.
|
||||
body: $body,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a text response.
|
||||
* @param string $value The text value to pass.
|
||||
* @param StatusCode $statusCode The status code to send back. OK by default.
|
||||
* @return ResponseInterface Built response.
|
||||
*/
|
||||
public function text(string $value, StatusCode $statusCode = StatusCode::OK): ResponseInterface
|
||||
{
|
||||
return $this->newResponse($statusCode, ContentType::Text->value, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a JSON response.
|
||||
* @param mixed $value The value to serialize.
|
||||
* @param StatusCode $statusCode The status code to send back. OK by default.
|
||||
* @return ResponseInterface Built response.
|
||||
*/
|
||||
public function json(mixed $value, StatusCode $statusCode = StatusCode::OK): ResponseInterface
|
||||
{
|
||||
return $this->newResponse($statusCode, ContentType::Json->value, json_encode($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an XML response.
|
||||
* @param DOMDocument $value The XML value to serialize.
|
||||
* @param StatusCode $statusCode The status code to send back. OK by default.
|
||||
* @return ResponseInterface Built response.
|
||||
*/
|
||||
public function xml(DOMDocument $value, StatusCode $statusCode = StatusCode::OK): ResponseInterface
|
||||
{
|
||||
return $this->newResponse($statusCode, ContentType::Json->value, $value->saveXML());
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a rendered response.
|
||||
* @param Renderer $renderer The renderer instance to use.
|
||||
* @param object $arguments Arguments to use when rendering.
|
||||
* @param StatusCode $statusCode The status code to send back. OK by default.
|
||||
* @param string|null $contentType The content type of the response.
|
||||
* @return ResponseInterface Built response.
|
||||
*/
|
||||
public function render(Renderer $renderer, object $arguments, StatusCode $statusCode = StatusCode::OK, ?string $contentType = null): ResponseInterface
|
||||
{
|
||||
return $this->newResponse($statusCode, $contentType ?? null, $renderer->render($arguments));
|
||||
}
|
||||
}
|
166
src/Router/Route.php
Normal file
166
src/Router/Route.php
Normal file
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http\Router;
|
||||
|
||||
use Nest\Application;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Route definition class.
|
||||
*/
|
||||
class Route
|
||||
{
|
||||
/**
|
||||
* List of allowed request methods.
|
||||
* All methods are allowed by default (empty array).
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $methods = [];
|
||||
|
||||
/**
|
||||
* List of allowed servers names.
|
||||
* All servers names are allowed by default (empty array).
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $serversNames = [];
|
||||
|
||||
/**
|
||||
* Request handler for defined route (URI parameters as function parameters).
|
||||
* @var callable(mixed...): ResponseInterface
|
||||
*/
|
||||
protected $handler;
|
||||
|
||||
/**
|
||||
* Create a route from its URI.
|
||||
* @param string $uri Route URI.
|
||||
*/
|
||||
public function __construct(protected string $uri)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Add allowed request methods (GET, POST, etc.).
|
||||
* @param string ...$method Allowed method.
|
||||
* @return $this
|
||||
*/
|
||||
public function method(string ...$method): static
|
||||
{
|
||||
array_push($this->methods, ...$method);
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* Allow all request methods.
|
||||
* @return $this
|
||||
*/
|
||||
public function allMethods(): static
|
||||
{
|
||||
$this->methods = [];
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* Allow GET request method.
|
||||
* @return $this
|
||||
*/
|
||||
public function get(): static
|
||||
{
|
||||
return $this->method("GET");
|
||||
}
|
||||
/**
|
||||
* Allow POST request method.
|
||||
* @return $this
|
||||
*/
|
||||
public function post(): static
|
||||
{
|
||||
return $this->method("POST");
|
||||
}
|
||||
/**
|
||||
* Allow PATCH request method.
|
||||
* @return $this
|
||||
*/
|
||||
public function patch(): static
|
||||
{
|
||||
return $this->method("PATCH");
|
||||
}
|
||||
/**
|
||||
* Allow DELETE request method.
|
||||
* @return $this
|
||||
*/
|
||||
public function delete(): static
|
||||
{
|
||||
return $this->method("DELETE");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset allowed servers names to all by default.
|
||||
* @return $this
|
||||
*/
|
||||
public function resetServersNames(): static
|
||||
{
|
||||
$this->serversNames = [];
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* Add an allowed server name for the given route.
|
||||
* @return $this
|
||||
*/
|
||||
public function serverName(string $serverName): static
|
||||
{
|
||||
$this->serversNames[] = $serverName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set request handler for given route.
|
||||
* @param callable(mixed...): ResponseInterface $handler Request handler. Will be called with parameters in defined URI.
|
||||
* @return $this
|
||||
*/
|
||||
public function handler(callable $handler): static
|
||||
{
|
||||
$this->handler = $handler;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a controller method as handler.
|
||||
* @param string $controllerClass The controller class.
|
||||
* @param string $controllerMethod The method to call in the controller.
|
||||
* @return $this
|
||||
*/
|
||||
public function controller(string $controllerClass, string $controllerMethod): static
|
||||
{
|
||||
$this->handler = function (...$args) use ($controllerClass, $controllerMethod) {
|
||||
return call_user_func_array([new $controllerClass(Application::get()), $controllerMethod], $args);
|
||||
};
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route URI.
|
||||
* @return string
|
||||
*/
|
||||
public function getUri(): string
|
||||
{
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get request handler for given route.
|
||||
* @return callable(mixed...): ResponseInterface Request handler. Will be called with parameters in defined URI.
|
||||
*/
|
||||
public function getHandler(): callable
|
||||
{
|
||||
return $this->handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the route matches a request.
|
||||
* @param ServerRequestInterface $request The request to match.
|
||||
* @return bool True if the route matches the request.
|
||||
*/
|
||||
public function match(ServerRequestInterface $request): bool
|
||||
{
|
||||
return (empty($this->methods) || in_array($request->getMethod(), $this->methods)) &&
|
||||
(empty($this->serversNames) || in_array($request->getUri()->getHost(), $this->serversNames)) &&
|
||||
!empty($this->handler);
|
||||
}
|
||||
}
|
27
src/Router/RouteMatch.php
Normal file
27
src/Router/RouteMatch.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http\Router;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* A matched route result.
|
||||
*/
|
||||
readonly class RouteMatch
|
||||
{
|
||||
/**
|
||||
* @param Route $route Matched route.
|
||||
* @param array<string, string> $params Matched URL parameters.
|
||||
*/
|
||||
public function __construct(public Route $route, public array $params)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Call the matched route handler with parameters.
|
||||
* @return ResponseInterface The retrieved response from route.
|
||||
*/
|
||||
public function __invoke(): ResponseInterface
|
||||
{
|
||||
return call_user_func_array($this->route->getHandler(), $this->params);
|
||||
}
|
||||
}
|
35
src/Router/Router.php
Normal file
35
src/Router/Router.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http\Router;
|
||||
|
||||
use Nest\Http\Exceptions\NotFoundException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Router HTTP request handler.
|
||||
*/
|
||||
class Router
|
||||
{
|
||||
/**
|
||||
* Define a new router request handler from the given routes.
|
||||
* @param Routes $routes Routes instance.
|
||||
*/
|
||||
public function __construct(protected Routes $routes)
|
||||
{
|
||||
// Define routes.
|
||||
$this->routes->define();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the request with the right route, if there is one.
|
||||
* @param ServerRequestInterface $request The request to handle.
|
||||
* @return ResponseInterface The request response.
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function __invoke(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$route = $this->routes->match($request);
|
||||
return $route();
|
||||
}
|
||||
}
|
79
src/Router/Routes.php
Normal file
79
src/Router/Routes.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http\Router;
|
||||
|
||||
use Nest\Http\Exceptions\NotFoundException;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Routes definition class.
|
||||
*/
|
||||
abstract class Routes
|
||||
{
|
||||
/**
|
||||
* Define routes.
|
||||
* @return void
|
||||
*/
|
||||
public abstract function define(): void;
|
||||
|
||||
/**
|
||||
* Main routes tree node.
|
||||
* @var RoutesTree
|
||||
*/
|
||||
protected RoutesTree $tree;
|
||||
|
||||
/**
|
||||
* Create a new routes definition object.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->tree = new RoutesTree("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the given URI it in route nodes names.
|
||||
* @return string[] Routes nodes names.
|
||||
*/
|
||||
private function splitUri(string $uri): array
|
||||
{
|
||||
$uri = trim($uri, "/ \n\r\t\v\0");
|
||||
return array_map(fn ($node) => trim($node, "/ \n\r\t\v\0"), explode("/", $uri));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new route for the given URI.
|
||||
* @param Route $route The route to add.
|
||||
* @return void
|
||||
*/
|
||||
protected function addRoute(Route $route): void
|
||||
{
|
||||
$nodes = $this->splitUri($route->getUri());
|
||||
// Register the new route with its split URI.
|
||||
$this->tree->register($nodes, $route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a new route.
|
||||
* @param string $uri The route URI.
|
||||
* @return Route The defined route.
|
||||
*/
|
||||
public function route(string $uri): Route
|
||||
{
|
||||
// Create a route with the given URI and add it to the list.
|
||||
$this->addRoute($route = new Route($uri));
|
||||
|
||||
// Return created route.
|
||||
return $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to match a defined route from the given request.
|
||||
* @param ServerRequestInterface $request The request to match.
|
||||
* @return RouteMatch The matched route.
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function match(ServerRequestInterface $request): RouteMatch
|
||||
{
|
||||
return $this->tree->match($this->splitUri($request->getUri()), $request);
|
||||
}
|
||||
}
|
158
src/Router/RoutesTree.php
Normal file
158
src/Router/RoutesTree.php
Normal file
|
@ -0,0 +1,158 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http\Router;
|
||||
|
||||
use Nest\Http\Exceptions\NotFoundException;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Routes tree structure class.
|
||||
*/
|
||||
class RoutesTree
|
||||
{
|
||||
/**
|
||||
* Current node (static or dynamic name).
|
||||
* @var string
|
||||
*/
|
||||
protected string $node;
|
||||
|
||||
/**
|
||||
* The defined routes for the current node.
|
||||
* @var Route[]
|
||||
*/
|
||||
protected array $routes = [];
|
||||
|
||||
/**
|
||||
* Static children of the current node.
|
||||
* @var array<string, RoutesTree>
|
||||
*/
|
||||
protected array $staticChildren = [];
|
||||
|
||||
/**
|
||||
* Dynamic children of the current node.
|
||||
* @var array<string, RoutesTree>
|
||||
*/
|
||||
protected array $dynamicChildren = [];
|
||||
|
||||
/**
|
||||
* Create a new route tree structure for the given node.
|
||||
* @param string $node Tree node name.
|
||||
*/
|
||||
public function __construct(string $node)
|
||||
{
|
||||
$this->node = $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current node is dynamic or static.
|
||||
* @return bool
|
||||
*/
|
||||
public function isDynamic(): bool
|
||||
{
|
||||
return $this->node[0] == ':';
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the matching route for the given request.
|
||||
* @param string[] $nodes The split URI of the route to match.
|
||||
* @param ServerRequestInterface $request The request to match.
|
||||
* @param array<string, string> $params URL parameters values.
|
||||
* @return RouteMatch Matching route.
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function match(array $nodes, ServerRequestInterface $request, array $params = []): RouteMatch
|
||||
{
|
||||
// Get the current subnode.
|
||||
$subNode = array_shift($nodes);
|
||||
if (!empty($subNode))
|
||||
// Trim the current URI part.
|
||||
$subNode = rawurldecode(trim($subNode, "/ \n\r\t\v\0"));
|
||||
|
||||
if (!empty($subNode))
|
||||
{ // There is a subnode, try to match a child.
|
||||
if (!empty($this->staticChildren[$subNode]))
|
||||
{ // Match a static child, trying to match on it.
|
||||
try
|
||||
{
|
||||
return $this->staticChildren[$subNode]->match($nodes, $request, $params);
|
||||
}
|
||||
catch (NotFoundException $notFound)
|
||||
{ // No match, trying to find another one.
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->dynamicChildren as $varName => $child)
|
||||
{ // Try to match every dynamic child.
|
||||
try
|
||||
{
|
||||
// Add subnode as route parameter.
|
||||
$params[substr($varName, 1)] = $subNode;
|
||||
return $child->match($nodes, $request, $params);
|
||||
}
|
||||
catch (NotFoundException $notFound)
|
||||
{ // No match, trying to find another one.
|
||||
}
|
||||
}
|
||||
|
||||
// Still no match, throw a not found exception.
|
||||
throw new NotFoundException();
|
||||
}
|
||||
else
|
||||
{ // No subnode, the matched route can be in this node.
|
||||
foreach ($this->routes as $route)
|
||||
{ // Trying each registered route for this node.
|
||||
if ($route->match($request))
|
||||
// Found a matching route.
|
||||
return new RouteMatch($route, $params);
|
||||
}
|
||||
|
||||
// No matching route found.
|
||||
throw new NotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any child with the given subnode name, if there is one.
|
||||
* @param string $subnode Subnode name.
|
||||
* @return RoutesTree|null The routes tree with the given subnode, or NULL if there's none.
|
||||
*/
|
||||
public function anyChild(string $subnode): ?RoutesTree
|
||||
{
|
||||
return $this->staticChildren[$subnode] ?? $this->dynamicChildren[$subnode] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new route for the given URI.
|
||||
* @param string[] $nodes The split URI of the route to register.
|
||||
* @param Route $route The route to register.
|
||||
* @return void
|
||||
*/
|
||||
public function register(array $nodes, Route $route): void
|
||||
{
|
||||
// Get the current subnode.
|
||||
$subNode = array_shift($nodes);
|
||||
if (!empty($subNode))
|
||||
// Trim the current URI part.
|
||||
$subNode = trim($subNode, "/ \n\r\t\v\0");
|
||||
|
||||
if (!empty($subNode))
|
||||
{ // There is a subnode to register, creating or getting its route tree and call register recursively.
|
||||
$child = $this->anyChild($subNode);
|
||||
if (empty($child))
|
||||
{ // No child for the current subnode, creating a new one.
|
||||
$child = new RoutesTree($subNode);
|
||||
if ($child->isDynamic())
|
||||
$this->dynamicChildren[$subNode] = $child;
|
||||
else
|
||||
$this->staticChildren[$subNode] = $child;
|
||||
}
|
||||
|
||||
// Register the route in the child tree.
|
||||
$child->register($nodes, $route);
|
||||
}
|
||||
else
|
||||
{ // We reached the end of the nodes list, registering the route in this tree.
|
||||
$this->routes[] = $route;
|
||||
}
|
||||
}
|
||||
}
|
79
src/StatusCode.php
Normal file
79
src/StatusCode.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Http;
|
||||
|
||||
/**
|
||||
* An HTTP status code.
|
||||
*/
|
||||
enum StatusCode: int
|
||||
{
|
||||
case Continue = 100;
|
||||
case SwitchingProtocols = 101;
|
||||
case Processing = 102;
|
||||
case EarlyHints = 103;
|
||||
|
||||
case OK = 200;
|
||||
case Created = 201;
|
||||
case Accepted = 202;
|
||||
case NonAuthoritativeInformation = 203;
|
||||
case NoContent = 204;
|
||||
case ResetContent = 205;
|
||||
case PartialContent = 206;
|
||||
case MultiStatus = 207;
|
||||
case AlreadyReported = 208;
|
||||
case IMUsed = 226;
|
||||
|
||||
case MultipleChoices = 300;
|
||||
case MovedPermanently = 301;
|
||||
case Found = 302;
|
||||
case SeeOther = 303;
|
||||
case NotModified = 304;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
case UseProxy = 305;
|
||||
case TemporaryRedirect = 307;
|
||||
case PermanentRedirect = 308;
|
||||
|
||||
case BadRequest = 400;
|
||||
case Unauthorized = 401;
|
||||
case PaymentRequired = 402;
|
||||
case Forbidden = 403;
|
||||
case NotFound = 404;
|
||||
case MethodNotAllowed = 405;
|
||||
case NotAcceptable = 406;
|
||||
case ProxyAuthenticationRequired = 407;
|
||||
case RequestTimeout = 408;
|
||||
case Conflict = 409;
|
||||
case Gone = 410;
|
||||
case LengthRequired = 411;
|
||||
case PreconditionFailed = 412;
|
||||
case ContentTooLarge = 413;
|
||||
case UriTooLong = 414;
|
||||
case UnsupportedMediaType = 415;
|
||||
case RangeNotSatisfiable = 416;
|
||||
case ExpectationFailed = 417;
|
||||
case Teapot = 418;
|
||||
case MisdirectedRequest = 421;
|
||||
case UnprocessableContent = 422;
|
||||
case Locked = 423;
|
||||
case FailedDependency = 424;
|
||||
case TooEarly = 425;
|
||||
case UpgradeRequired = 426;
|
||||
case PreconditionRequired = 428;
|
||||
case TooManyRequests = 429;
|
||||
case HeaderFieldsTooLarge = 431;
|
||||
case UnavailableForLegalReasons = 451;
|
||||
|
||||
case InternalServerError = 500;
|
||||
case NotImplemented = 501;
|
||||
case BadGateway = 502;
|
||||
case ServiceUnavailable = 503;
|
||||
case GatewayTimeout = 504;
|
||||
case HttpVersionNotSupported = 505;
|
||||
case VariantAlsoNegotiates = 506;
|
||||
case InsufficientStorage = 507;
|
||||
case LoopDetected = 508;
|
||||
case NotExtended = 510;
|
||||
case NetworkAuthenticationRequired = 511;
|
||||
}
|
51
src/views/exception.php
Normal file
51
src/views/exception.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
/**
|
||||
* @var \Nest\Http\Exceptions\HttpException $exception
|
||||
*/
|
||||
?>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title><?= $exception->getErrorId() ?></title>
|
||||
<style>
|
||||
html, body
|
||||
{
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
|
||||
strong
|
||||
{
|
||||
display: block;
|
||||
margin: 1em auto;
|
||||
font-size: 1.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
pre
|
||||
{
|
||||
font-size: 1rem;
|
||||
font-family: monospace;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
footer
|
||||
{
|
||||
margin: 4em auto;
|
||||
color: grey;
|
||||
font-size: 0.8em;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<strong>
|
||||
<span class="code"><?= $exception->getStatusCode() ?></span> <span class="id"><?= $exception->getErrorId() ?></span>
|
||||
</strong>
|
||||
<pre class="message"><?= $exception->getMessage() ?></pre>
|
||||
<footer>
|
||||
Nest
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Reference in a new issue