diff --git a/http-core/Abstracts/EventDispatchHelper.php b/http-core/Abstracts/EventDispatchHelper.php
new file mode 100644
index 00000000..6dcf615b
--- /dev/null
+++ b/http-core/Abstracts/EventDispatchHelper.php
@@ -0,0 +1,16 @@
+HTTP 404 Not Found
Powered by Swoole', $code);
+ }
+
+}
diff --git a/http-core/Abstracts/ResponseHelper.php b/http-core/Abstracts/ResponseHelper.php
new file mode 100644
index 00000000..1e8bf20c
--- /dev/null
+++ b/http-core/Abstracts/ResponseHelper.php
@@ -0,0 +1,24 @@
+__call__()->{$name}(...$arguments);
+ }
+
+
+ /**
+ * @param $name
+ * @return mixed
+ */
+ public function __get($name): mixed
+ {
+ // TODO: Change the autogenerated stub
+ return $this->__call__()->{$name};
+ }
+
+
+ /**
+ * @param \Swoole\Http\Request $request
+ * @return Request
+ * @throws \Exception
+ */
+ public static function create(\Swoole\Http\Request $request): Request
+ {
+ $serverRequest = ServerRequest::createServerRequest($request);
+
+ Context::setContext(ResponseInterface::class, new Response());
+
+ Context::setContext(RequestInterface::class, $serverRequest);
+
+ return Kiri::getDi()->get(Request::class);
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getProtocolVersion(): string
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @param string $version
+ * @return Request
+ */
+ public function withProtocolVersion($version): RequestInterface
+ {
+ return $this->__call__()->{__FUNCTION__}($version);
+ }
+
+
+ /**
+ * @return \string[][]
+ */
+ public function getHeaders(): array
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @param string $name
+ * @return bool
+ */
+ public function hasHeader($name): bool
+ {
+ return $this->__call__()->{__FUNCTION__}($name);
+ }
+
+
+ /**
+ * @param string $name
+ * @return string[]
+ */
+ public function getHeader($name): array
+ {
+ return $this->__call__()->{__FUNCTION__}($name);
+ }
+
+
+ /**
+ * @param string $name
+ * @return string
+ */
+ public function getHeaderLine($name): string
+ {
+ return $this->__call__()->{__FUNCTION__}($name);
+ }
+
+
+ /**
+ * @param string $name
+ * @param string|string[] $value
+ * @return Request
+ */
+ public function withHeader($name, $value): RequestInterface
+ {
+ return $this->__call__()->{__FUNCTION__}($name, $value);
+ }
+
+
+ /**
+ * @param string $name
+ * @param string|string[] $value
+ * @return Request
+ */
+ public function withAddedHeader($name, $value): RequestInterface
+ {
+ return $this->__call__()->{__FUNCTION__}($name, $value);
+ }
+
+
+ /**
+ * @param string $name
+ * @return Request
+ */
+ public function withoutHeader($name): RequestInterface
+ {
+ return $this->__call__()->{__FUNCTION__}($name);
+ }
+
+
+ /**
+ * @return StreamInterface
+ */
+ public function getBody(): StreamInterface
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @param StreamInterface $body
+ * @return Request
+ */
+ public function withBody(StreamInterface $body): RequestInterface
+ {
+ return $this->__call__()->{__FUNCTION__}($body);
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getRequestTarget(): string
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @param mixed $requestTarget
+ * @return Request
+ */
+ public function withRequestTarget($requestTarget): RequestInterface
+ {
+ return $this->__call__()->{__FUNCTION__}($requestTarget);
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getMethod(): string
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @param string $method
+ * @return bool
+ */
+ public function isMethod(string $method): bool
+ {
+ return $this->__call__()->{__FUNCTION__}($method);
+ }
+
+
+ /**
+ * @param string $method
+ * @return Request
+ */
+ public function withMethod($method): RequestInterface
+ {
+ return $this->__call__()->{__FUNCTION__}($method);
+ }
+
+
+ /**
+ * @return UriInterface
+ */
+ public function getUri(): UriInterface
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @param UriInterface $uri
+ * @param false $preserveHost
+ * @return Request
+ */
+ public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
+ {
+ return $this->__call__()->{__FUNCTION__}($uri, $preserveHost);
+ }
+
+
+ /**
+ * @param string $name
+ * @return UploadedFileInterface|null
+ */
+ public function file(string $name): ?UploadedFileInterface
+ {
+ $files = $this->__call__()->getUploadedFiles();
+ if (empty($files) || !isset($files[$name])) {
+ return null;
+ }
+ return new Uploaded($files[$name]['tmp_name'], $files[$name]['name'], $files[$name]['type'],
+ $files[$name]['size'], $files[$name]['error']);
+ }
+
+
+ /**
+ * @param string|null $name
+ * @param mixed|null $default
+ * @return mixed
+ */
+ private function _getParsedBody(string|null $name = null, mixed $default = null): mixed
+ {
+ $body = $this->__call__()->getParsedBody();
+ if (empty($name)) {
+ return $body;
+ }
+ return $body[$name] ?? $default;
+ }
+
+
+ /**
+ * @return array
+ */
+ public function all(): array
+ {
+ return $this->_getParsedBody();
+ }
+
+
+ /**
+ * @param string $name
+ * @param bool|int|string|null $default
+ * @return mixed
+ */
+ public function query(string $name, bool|int|string|null $default = null): mixed
+ {
+ $files = $this->__call__()->getQueryParams();
+
+ return $files[$name] ?? $default;
+ }
+
+
+ /**
+ * @param string $name
+ * @param int|bool|array|string|null $default
+ * @return mixed
+ */
+ public function post(string $name, int|bool|array|string|null $default = null): mixed
+ {
+ return $this->_getParsedBody($name, $default);
+ }
+
+
+ /**
+ * @param string $name
+ * @param bool $required
+ * @return int|null
+ * @throws \Exception
+ */
+ public function int(string $name, bool $required = false): ?int
+ {
+ $int = $this->_getParsedBody($name);
+ if (is_null($int) && $required) {
+ throw new \Exception('Required param "' . $name . '"');
+ }
+ return (int)$int;
+ }
+
+
+ /**
+ * @param string $name
+ * @param bool $required
+ * @return float|null
+ * @throws \Exception
+ */
+ public function float(string $name, bool $required = false): ?float
+ {
+ $int = $this->_getParsedBody($name);
+ if (is_null($int) && $required) {
+ throw new \Exception('Required param "' . $name . '"');
+ }
+ return (float)$int;
+ }
+
+
+ /**
+ * @param string $name
+ * @param bool $required
+ * @return string|null
+ * @throws \Exception
+ */
+ public function date(string $name, bool $required = false): ?string
+ {
+ $int = $this->_getParsedBody($name);
+ if (is_null($int) && $required) {
+ throw new \Exception('Required param "' . $name . '"');
+ }
+ return (string)$int;
+ }
+
+
+ /**
+ * @param string $name
+ * @param bool $required
+ * @return int|null
+ * @throws \Exception
+ */
+ public function timestamp(string $name, bool $required = false): ?int
+ {
+ $int = $this->_getParsedBody($name);
+ if (is_null($int) && $required) {
+ throw new \Exception('Required param "' . $name . '"');
+ }
+ return (int)$int;
+ }
+
+
+ /**
+ * @param string $name
+ * @param bool $required
+ * @return string|null
+ * @throws \Exception
+ */
+ public function string(string $name, bool $required = false): ?string
+ {
+ $int = $this->_getParsedBody($name);
+ if (is_null($int) && $required) {
+ throw new \Exception('Required param "' . $name . '"');
+ }
+ return (string)$int;
+ }
+
+
+ /**
+ * @param string $name
+ * @param array $default
+ * @return array|null
+ */
+ public function array(string $name, array $default = []): ?array
+ {
+ $int = $this->_getParsedBody($name);
+ if (is_null($int)) {
+ return $default;
+ }
+ return $int;
+ }
+
+
+ /**
+ * @return array|null
+ */
+ public function gets(): ?array
+ {
+ return $this->__call__()->getQueryParams();
+ }
+
+
+ /**
+ * @param string $field
+ * @param string $sizeField
+ * @param int $max
+ * @return float|int
+ */
+ public function offset(string $field = 'page', string $sizeField = 'size', int $max = 100): float|int
+ {
+ $page = $this->query($field);
+ $size = $this->size($sizeField, $max);
+ $offset = ($page - 1) * $size;
+ if ($offset < 0) {
+ $offset = 0;
+ }
+ return $offset;
+ }
+
+
+ /**
+ * @param string $field
+ * @param int $max
+ * @return int
+ */
+ public function size(string $field = 'size', int $max = 100): int
+ {
+ $size = $this->query($field);
+ if ($size > $max) {
+ $size = $max;
+ }
+ return $size;
+ }
+
+
+ /**
+ * @param $name
+ * @param null $default
+ * @return mixed
+ */
+ public function input($name, $default = null): mixed
+ {
+ return $this->_getParsedBody($name, $default);
+ }
+
+
+ /**
+ * @return float
+ */
+ #[Pure] public function getStartTime(): float
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @param AuthIdentity $authority
+ */
+ public function setAuthority(AuthIdentity $authority): void
+ {
+ $this->__call__()->{__FUNCTION__}($authority);
+ }
+
+
+ /**
+ * @return int
+ */
+ public function getClientId(): int
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @return string|null
+ */
+ #[Pure] public function getAccessControlAllowOrigin(): ?string
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @return string|null
+ */
+ #[Pure] public function getAccessControlAllowHeaders(): ?string
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @return string|null
+ */
+ #[Pure] public function getAccessControlRequestMethod(): ?string
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+}
diff --git a/http-core/Constrict/RequestInterface.php b/http-core/Constrict/RequestInterface.php
new file mode 100644
index 00000000..d48d3bf9
--- /dev/null
+++ b/http-core/Constrict/RequestInterface.php
@@ -0,0 +1,155 @@
+ ContentType::JSON,
+ 'charset' => 'utf-8'
+ ]);
+ $this->withContentType($contentType['format'] ?? ContentType::JSON)
+ ->withCharset($contentType['charset'] ?? 'utf-8');
+ }
+
+
+ /**
+ * @param string $name
+ * @return mixed
+ */
+ public function __get(string $name)
+ {
+ return $this->__call__()->{$name};
+ }
+
+
+ /**
+ * @return Psr7Response
+ */
+ public function __call__(): Psr7Response
+ {
+ return Context::getContext(ResponseInterface::class, new Psr7Response());
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getProtocolVersion(): string
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @param string $version
+ * @return ResponseInterface|Psr7Response
+ */
+ public function withProtocolVersion($version): ResponseInterface|Psr7Response
+ {
+ return $this->__call__()->{__FUNCTION__}($version);
+ }
+
+
+ /**
+ * @return array
+ */
+ public function getHeaders(): array
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @param string $name
+ * @return bool
+ */
+ public function hasHeader($name): bool
+ {
+ return $this->__call__()->{__FUNCTION__}($name);
+ }
+
+
+ /**
+ * @param string $name
+ * @return string
+ */
+ public function getHeader($name): string
+ {
+ return $this->__call__()->{__FUNCTION__}($name);
+ }
+
+
+ /**
+ * @param string $name
+ * @return string
+ */
+ public function getHeaderLine($name): string
+ {
+ return $this->__call__()->{__FUNCTION__}($name);
+ }
+
+
+ /**
+ * @param string $name
+ * @param string|string[] $value
+ * @return ResponseInterface|Psr7Response
+ */
+ public function withHeader($name, $value): ResponseInterface|Psr7Response
+ {
+ return $this->__call__()->{__FUNCTION__}($name, $value);
+ }
+
+
+ /**
+ * @param string $name
+ * @param string|string[] $value
+ * @return ResponseInterface|Psr7Response
+ */
+ public function withAddedHeader($name, $value): ResponseInterface|Psr7Response
+ {
+ return $this->__call__()->{__FUNCTION__}($name, $value);
+ }
+
+
+ /**
+ * @param string $name
+ * @return ResponseInterface|Psr7Response
+ */
+ public function withoutHeader($name): ResponseInterface|Psr7Response
+ {
+ return $this->__call__()->{__FUNCTION__}($name);
+ }
+
+
+ /**
+ * @return StreamInterface
+ */
+ public function getBody(): StreamInterface
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @param StreamInterface $body
+ * @return ResponseInterface|Psr7Response
+ */
+ public function withBody(StreamInterface $body): ResponseInterface|Psr7Response
+ {
+ return $this->__call__()->{__FUNCTION__}($body);
+ }
+
+ /**
+ * @return int
+ */
+ public function getStatusCode(): int
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @param int $code
+ * @param string $reasonPhrase
+ * @return ResponseInterface|Psr7Response
+ */
+ public function withStatus($code, $reasonPhrase = ''): ResponseInterface|Psr7Response
+ {
+ return $this->__call__()->{__FUNCTION__}($code, $reasonPhrase);
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getReasonPhrase(): string
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+ /**
+ * @param string $path
+ * @return OnDownloadInterface
+ */
+ public function file(string $path): OnDownloadInterface
+ {
+ return $this->__call__()->{__FUNCTION__}($path);
+ }
+
+ /**
+ * @param $responseData
+ * @return string|array|bool|int|null
+ */
+ public function _toArray($responseData): string|array|null|bool|int
+ {
+ return $this->__call__()->{__FUNCTION__}($responseData);
+ }
+
+ /**
+ * @param $data
+ * @return ResponseInterface
+ */
+ public function xml($data): ResponseInterface
+ {
+ return $this->__call__()->{__FUNCTION__}($data);
+ }
+
+ /**
+ * @param $data
+ * @return ResponseInterface
+ */
+ public function html($data): ResponseInterface
+ {
+ return $this->__call__()->{__FUNCTION__}($data);
+ }
+
+ /**
+ * @param $data
+ * @return ResponseInterface
+ */
+ public function json($data): ResponseInterface
+ {
+ return $this->__call__()->{__FUNCTION__}($data);
+ }
+
+ /**
+ * @return string
+ */
+ public function getContentType(): string
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasContentType(): bool
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @param string $type
+ * @return ResponseInterface
+ */
+ public function withContentType(string $type): ResponseInterface
+ {
+ return $this->__call__()->{__FUNCTION__}($type);
+ }
+
+
+ /**
+ * @param string|null $value
+ * @return ResponseInterface
+ */
+ public function withAccessControlAllowOrigin(?string $value): ResponseInterface
+ {
+ return $this->__call__()->{__FUNCTION__}($value);
+ }
+
+
+ /**
+ * @param string|null $value
+ * @return ResponseInterface
+ */
+ public function withAccessControlRequestMethod(?string $value): ResponseInterface
+ {
+ return $this->__call__()->{__FUNCTION__}($value);
+ }
+
+
+ /**
+ * @param string|null $value
+ * @return ResponseInterface
+ */
+ public function withAccessControlAllowHeaders(?string $value): ResponseInterface
+ {
+ return $this->__call__()->{__FUNCTION__}($value);
+ }
+
+
+ /**
+ * @return string|null
+ */
+ #[Pure] public function getAccessControlAllowOrigin(): ?string
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @return string|null
+ */
+ #[Pure] public function getAccessControlAllowHeaders(): ?string
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @return string|null
+ */
+ #[Pure] public function getAccessControlRequestMethod(): ?string
+ {
+ return $this->__call__()->{__FUNCTION__}();
+ }
+
+
+ /**
+ * @return int
+ */
+ public function getClientId(): int
+ {
+ if (!Context::hasContext('client.id.property')) {
+ $request = Context::getContext(RequestInterface::class, new RequestMessage());
+ return Context::setContext('client.id.property', $request->getClientId());
+ }
+ return (int)Context::getContext('client.id.property');
+ }
+
+
+ /**
+ * @return array
+ */
+ public function getClientInfo(): array
+ {
+ if (!Context::hasContext('client.info.property')) {
+ $request = Context::getContext(RequestInterface::class, new RequestMessage());
+
+ $server = Kiri::getDi()->get(ServerManager::class)->getServer();
+
+ $clientInfo = $server->getClientInfo($request->getClientId());
+
+ return Context::setContext('client.info.property', $clientInfo);
+ }
+ return Context::getContext('client.info.property');
+ }
+
+}
diff --git a/http-core/Constrict/ResponseEmitter.php b/http-core/Constrict/ResponseEmitter.php
new file mode 100644
index 00000000..b18015ec
--- /dev/null
+++ b/http-core/Constrict/ResponseEmitter.php
@@ -0,0 +1,50 @@
+getHeaders())) {
+ foreach ($emitter->getHeaders() as $name => $values) {
+ $response->header($name, implode(';', $values));
+ }
+ }
+ if (is_array($emitter->getCookieParams())) {
+ foreach ($emitter->getCookieParams() as $name => $cookie) {
+ $response->cookie($name, ...$cookie);
+ }
+ }
+ $response->setStatusCode($emitter->getStatusCode());
+ $response->header('Server', 'swoole');
+ $response->header('Swoole-Version', swoole_version());
+ if (!($emitter instanceof OnDownloadInterface)) {
+ $response->end($emitter->getBody()->getContents());
+ } else {
+ $emitter->dispatch($response);
+ }
+ }
+
+}
diff --git a/http-core/Constrict/ResponseInterface.php b/http-core/Constrict/ResponseInterface.php
new file mode 100644
index 00000000..b411af99
--- /dev/null
+++ b/http-core/Constrict/ResponseInterface.php
@@ -0,0 +1,105 @@
+withContentType(ContentType::HTML)->withCharset('utf-8');
+ if ($exception->getCode() == 404) {
+ return $response->withBody(new Stream($exception->getMessage()))
+ ->withStatus(404);
+ }
+ $code = $exception->getCode() == 0 ? 500 : $exception->getCode();
+ return $response->withBody(new Stream(jTraceEx($exception, null, true)))
+ ->withStatus($code);
+ }
+
+}
diff --git a/http-core/Handler/Abstracts/BaseContext.php b/http-core/Handler/Abstracts/BaseContext.php
new file mode 100644
index 00000000..78f00ef6
--- /dev/null
+++ b/http-core/Handler/Abstracts/BaseContext.php
@@ -0,0 +1,13 @@
+middlewares) || !isset($this->middlewares[$this->offset])) {
+ return $this->dispatcher($request);
+ }
+
+ $middleware = $this->middlewares[$this->offset];
+ if (!($middleware instanceof MiddlewareInterface)) {
+ throw new Exception('get_implements_class($middleware) not found method process.');
+ }
+
+ $this->offset++;
+
+ return $middleware->process($request, $this);
+ }
+
+
+ /**
+ * @param ServerRequestInterface $request
+ * @return mixed
+ * @throws Exception
+ */
+ public function dispatcher(ServerRequestInterface $request): mixed
+ {
+ $response = call_user_func($this->handler->callback, ...$this->handler->params);
+ if (!($response instanceof ResponseInterface)) {
+ $response = $this->transferToResponse($response);
+ }
+ $response->withHeader('Run-Time', $this->_runTime($request));
+ return $response;
+ }
+
+
+ /**
+ * @param ServerRequest $request
+ * @return float
+ */
+ private function _runTime(ServerRequestInterface $request): float
+ {
+ $float = microtime(true) - time();
+
+ $serverParams = $request->getServerParams();
+
+ $rTime = $serverParams['request_time_float'] - $serverParams['request_time'];
+
+ return round($float - $rTime, 6);
+ }
+
+
+ /**
+ * @param mixed $responseData
+ * @return \Server\Constrict\ResponseInterface
+ * @throws Exception
+ */
+ private function transferToResponse(mixed $responseData): ResponseInterface
+ {
+ $interface = response()->withStatus(200);
+ if (!$interface->hasContentType()) {
+ $interface->withContentType('application/json;charset=utf-8');
+ }
+ if (str_contains($interface->getContentType(), 'xml')) {
+ if (is_object($responseData)) {
+ $responseData = get_object_vars($responseData);
+ }
+ $interface->getBody()->write(Help::toXml($responseData));
+ } else if (is_array($responseData)) {
+ $interface->getBody()->write(json_encode($responseData));
+ } else {
+ $interface->getBody()->write((string)$responseData);
+ }
+ return $interface;
+ }
+
+
+}
diff --git a/http-core/Handler/Abstracts/HandlerManager.php b/http-core/Handler/Abstracts/HandlerManager.php
new file mode 100644
index 00000000..742cedee
--- /dev/null
+++ b/http-core/Handler/Abstracts/HandlerManager.php
@@ -0,0 +1,70 @@
+ $handlers) {
+ $array[] = [
+ 'path' => $path,
+ 'method' => implode(',', array_keys($handlers))
+ ];
+ }
+ return $array;
+ }
+
+}
diff --git a/http-core/Handler/Abstracts/HttpService.php b/http-core/Handler/Abstracts/HttpService.php
new file mode 100644
index 00000000..dc026ae4
--- /dev/null
+++ b/http-core/Handler/Abstracts/HttpService.php
@@ -0,0 +1,52 @@
+getLogger();
+ $logger->write($message, $category);
+ }
+
+ /**
+ * @param $name
+ * @return mixed
+ * @throws Exception
+ */
+ public function __get($name): mixed
+ {
+ if (method_exists($this, $name)) {
+ return $this->{$name}();
+ }
+ $handler = 'get' . ucfirst($name);
+ if (method_exists($this, $handler)) {
+ return $this->{$handler}();
+ }
+ if (property_exists($this, $name)) {
+ return $this->$name;
+ }
+ $message = sprintf('method %s::%s not exists.', static::class, $name);
+ throw new Exception($message);
+ }
+
+}
diff --git a/http-core/Handler/Abstracts/Middleware.php b/http-core/Handler/Abstracts/Middleware.php
new file mode 100644
index 00000000..a533aa9b
--- /dev/null
+++ b/http-core/Handler/Abstracts/Middleware.php
@@ -0,0 +1,20 @@
+
+ */
+ private static array $_middlewares = [];
+
+
+ /**
+ * @param $class
+ * @param $method
+ * @param array|string|null $middlewares
+ * @return bool
+ */
+ public static function add($class, $method, array|string|null $middlewares): bool
+ {
+ [$class, $method] = static::setDefault($class, $method);
+ if (empty($middlewares)) {
+ return false;
+ }
+ if (is_string($middlewares)) {
+ $middlewares = [$middlewares];
+ }
+ $source = &static::$_middlewares[$class][$method];
+ foreach ($middlewares as $middleware) {
+ $middleware = di($middleware);
+ if (in_array($middleware, $source)) {
+ continue;
+ }
+ $source[] = $middleware;
+ }
+ return true;
+ }
+
+
+ /**
+ * @param $class
+ * @param $method
+ * @return array
+ */
+ private static function setDefault($class, $method): array
+ {
+ if (is_object($class)) {
+ $class = $class::class;
+ }
+ if (!isset(static::$_middlewares[$class])) {
+ static::$_middlewares[$class] = [];
+ }
+ if (!isset(static::$_middlewares[$class][$method])) {
+ static::$_middlewares[$class][$method] = [];
+ }
+ return [$class, $method];
+ }
+
+
+ /**
+ * @param $handler
+ * @return Iterator|null
+ */
+ public static function get($handler): ?array
+ {
+ if (!($handler instanceof Closure)) {
+ return static::$_middlewares[$handler[0]][$handler[1]] ?? null;
+ }
+ return null;
+ }
+
+
+}
diff --git a/http-core/Handler/Annotation/ControllerTarget.php b/http-core/Handler/Annotation/ControllerTarget.php
new file mode 100644
index 00000000..8fe601af
--- /dev/null
+++ b/http-core/Handler/Annotation/ControllerTarget.php
@@ -0,0 +1,9 @@
+getAccessControlRequestMethod();
+ $allowHeaders = $request->getAccessControlAllowHeaders();
+
+ if (empty($requestMethod)) $requestMethod = '*';
+ if (empty($allowHeaders)) $allowHeaders = '*';
+
+ $this->response->withAccessControlAllowOrigin('*')->withAccessControlRequestMethod($requestMethod)
+ ->withAccessControlAllowHeaders($allowHeaders);
+
+ return $handler->handle($request);
+ }
+
+}
diff --git a/http-core/Handler/DataGrip.php b/http-core/Handler/DataGrip.php
new file mode 100644
index 00000000..9d3aaf66
--- /dev/null
+++ b/http-core/Handler/DataGrip.php
@@ -0,0 +1,10 @@
+execute($request);
+ }
+}
diff --git a/http-core/Handler/Formatter/FileFormatter.php b/http-core/Handler/Formatter/FileFormatter.php
new file mode 100644
index 00000000..b7a59678
--- /dev/null
+++ b/http-core/Handler/Formatter/FileFormatter.php
@@ -0,0 +1,50 @@
+data = $context;
+ return $this;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getData(): mixed
+ {
+ $data = $this->data;
+ $this->clear();
+ return $data;
+ }
+
+
+ public function clear(): void
+ {
+ $this->data = null;
+ unset($this->data);
+ }
+}
diff --git a/http-core/Handler/Formatter/HtmlFormatter.php b/http-core/Handler/Formatter/HtmlFormatter.php
new file mode 100644
index 00000000..cf7bc317
--- /dev/null
+++ b/http-core/Handler/Formatter/HtmlFormatter.php
@@ -0,0 +1,61 @@
+data = $context;
+ return $this;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getData(): mixed
+ {
+ $data = $this->data;
+ $this->clear();
+ return $data;
+ }
+
+ public function clear(): void
+ {
+ $this->data = null;
+ unset($this->data);
+ }
+}
diff --git a/http-core/Handler/Formatter/IFormatter.php b/http-core/Handler/Formatter/IFormatter.php
new file mode 100644
index 00000000..9d3a2702
--- /dev/null
+++ b/http-core/Handler/Formatter/IFormatter.php
@@ -0,0 +1,33 @@
+data = $context;
+ return $this;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getData(): mixed
+ {
+ $data = $this->data;
+ $this->clear();
+ return $data;
+ }
+
+
+ public function clear(): void
+ {
+ $this->data = null;
+ unset($this->data);
+ }
+}
diff --git a/http-core/Handler/Formatter/XmlFormatter.php b/http-core/Handler/Formatter/XmlFormatter.php
new file mode 100644
index 00000000..b010cc00
--- /dev/null
+++ b/http-core/Handler/Formatter/XmlFormatter.php
@@ -0,0 +1,89 @@
+');
+
+ $this->toXml($dom, $context);
+
+ $this->data = $dom->saveXML();
+ }
+ return $this;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getData(): ?string
+ {
+ $data = $this->data;
+ $this->clear();
+ return $data;
+ }
+
+ /**
+ * @param SimpleXMLElement $dom
+ * @param $data
+ */
+ public function toXml(SimpleXMLElement $dom, $data)
+ {
+ foreach ($data as $key => $val) {
+ if (is_numeric($key)) {
+ $key = 'item' . $key;
+ }
+ if (is_array($val)) {
+ $node = $dom->addChild($key);
+ $this->toXml($node, $val);
+ } else if (is_object($val)) {
+ $val = get_object_vars($val);
+ $node = $dom->addChild($key);
+ $this->toXml($node, $val);
+ } else {
+ $dom->addChild($key, htmlspecialchars((string)$val));
+ }
+ }
+ }
+
+ public function clear(): void
+ {
+ $this->data = null;
+ unset($this->data);
+ }
+}
diff --git a/http-core/Handler/Handler.php b/http-core/Handler/Handler.php
new file mode 100644
index 00000000..fdc6f67a
--- /dev/null
+++ b/http-core/Handler/Handler.php
@@ -0,0 +1,94 @@
+route = $route;
+
+ $this->_injectParams($callback);
+
+ $this->callback = $callback;
+
+ $dispatcher = Kiri::getDi()->get(EventProvider::class);
+ $dispatcher->on(OnAfterWorkerStart::class, function () {
+ if ($this->callback instanceof Closure) {
+ return;
+ }
+ $this->_middlewares = MiddlewareManager::get($this->callback);
+
+ $aspect = NoteManager::getSpecify_annotation(Aspect::class, $this->callback[0], $this->callback[1]);
+
+ $this->callback[0] = Kiri::getDi()->get($this->callback[0]);
+ if (!is_null($aspect)) {
+ $this->recover($aspect);
+ }
+ });
+ }
+
+
+ /**
+ * @param Aspect $aspect
+ */
+ public function recover(Aspect $aspect)
+ {
+ $aspect = Kiri::getDi()->get($aspect->aspect);
+ if (empty($aspect)) {
+ return;
+ }
+ $callback = $this->callback;
+ $params = $this->params;
+
+ $this->params = [];
+ $this->callback = static function () use ($aspect, $callback, $params) {
+ $aspect->before();
+ $result = $aspect->invoke($callback, $params);
+ $aspect->after($result);
+ return $result;
+ };
+ }
+
+
+ /**
+ * @param array|Closure $callback
+ * @throws \ReflectionException
+ */
+ private function _injectParams(array|Closure $callback)
+ {
+ $container = Kiri::getDi();
+ if (!($callback instanceof Closure)) {
+ $this->params = $container->getMethodParameters($callback[0], $callback[1]);
+ } else {
+ $this->params = $container->getFunctionParameters($callback);
+ }
+ }
+}
diff --git a/http-core/Handler/Pipeline.php b/http-core/Handler/Pipeline.php
new file mode 100644
index 00000000..7a74c389
--- /dev/null
+++ b/http-core/Handler/Pipeline.php
@@ -0,0 +1,179 @@
+passable = $passable;
+ return $this;
+ }
+
+
+ /**
+ * @param $middle
+ * @return $this
+ */
+ public function overall($middle): static
+ {
+ $this->overall = $middle;
+ return $this;
+ }
+
+
+ /**
+ * 调用栈
+ * @param $pipes
+ * @return $this
+ */
+ public function through($pipes): static
+ {
+ if (empty($pipes)) return $this;
+ if (empty($this->pipes)) {
+ $this->pipes = is_array($pipes) ? $pipes : func_get_args();
+ } else {
+ foreach ($pipes as $pipe) {
+ $this->pipes[] = $pipe;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * 执行
+ * @param callable $destination
+ * @return static
+ */
+ public function then(callable $destination): static
+ {
+ $parameters = $this->passable;
+ if (!empty($this->overall)) {
+ array_unshift($this->pipes, $this->overall);
+ }
+ if (is_array($destination)) {
+ $destination = $this->aspect_caller($destination, $parameters);
+ }
+ $this->pipeline = array_reduce(array_reverse($this->pipes), $this->carry(),
+ static function () use ($destination, $parameters) {
+ return call_user_func($destination, ...$parameters);
+ }
+ );
+ return $this->clear();
+ }
+
+
+ /**
+ * @return $this
+ */
+ private function clear(): static
+ {
+ $this->pipes = [];
+ $this->passable = null;
+ $this->overall = null;
+ return $this;
+ }
+
+
+ /**
+ * @param $destination
+ * @param $parameters
+ * @return Closure|array
+ */
+ private function aspect_caller($destination, $parameters): Closure|array
+ {
+ [$controller, $action] = $destination;
+ /** @var Aspect $aop */
+ $aop = NoteManager::getSpecify_annotation(Aspect::class, $controller::class, $action);
+ if (!empty($aop)) {
+ $aop = Kiri::getDi()->get($aop->aspect);
+ $destination = static function () use ($aop, $destination, $parameters) {
+ /** @var IAspect $aop */
+ $aop->before();
+ $aop->after($data = $aop->invoke($destination, $parameters));
+ return $data;
+ };
+ }
+ return $destination;
+ }
+
+
+ /**
+ * @param $request
+ * @return mixed
+ */
+ public function interpreter($request): mixed
+ {
+ return call_user_func($this->pipeline, $request);
+ }
+
+
+ /**
+ * 设置异常处理器
+ * @param callable $handler
+ * @return $this
+ */
+ public function whenException(callable $handler): static
+ {
+ $this->exceptionHandler = $handler;
+ return $this;
+ }
+
+
+ /**
+ * @return Closure
+ */
+ protected function carry(): Closure
+ {
+ return static function ($stack, $pipe) {
+ return static function ($passable) use ($stack, $pipe) {
+ if ($pipe instanceof MiddlewareInterface) {
+ $pipe = [$pipe, 'process'];
+ }
+ return $pipe($passable, $stack);
+ };
+ };
+ }
+
+ /**
+ * 异常处理
+ * @param $passable
+ * @param Throwable $e
+ * @return mixed
+ * @throws Throwable
+ */
+ protected function handleException($passable, Throwable $e): mixed
+ {
+ if ($this->exceptionHandler) {
+ return call_user_func($this->exceptionHandler, $passable, $e);
+ }
+ throw $e;
+ }
+
+}
diff --git a/http-core/Handler/Router.php b/http-core/Handler/Router.php
new file mode 100644
index 00000000..15682e4b
--- /dev/null
+++ b/http-core/Handler/Router.php
@@ -0,0 +1,263 @@
+get(Router::class);
+ $router->addRoute('SOCKET', $route, $handler);
+ }
+
+
+ /**
+ * @param $route
+ * @param $handler
+ * @return void
+ * @throws
+ */
+ public static function post($route, $handler): void
+ {
+ $router = Kiri::getDi()->get(Router::class);
+ $router->addRoute('POST', $route, $handler);
+ }
+
+ /**
+ * @param $route
+ * @param $handler
+ * @return void
+ * @throws
+ */
+ public static function get($route, $handler): void
+ {
+ $router = Kiri::getDi()->get(Router::class);
+ $router->addRoute('GET', $route, $handler);
+ }
+
+
+ /**
+ * @param $route
+ * @param $handler
+ * @return void
+ * @throws
+ */
+ public static function options($route, $handler): void
+ {
+ $router = Kiri::getDi()->get(Router::class);
+ $router->addRoute('OPTIONS', $route, $handler);
+ }
+
+
+ /**
+ * @param $route
+ * @param $handler
+ * @throws
+ */
+ public static function any($route, $handler): void
+ {
+ $router = Kiri::getDi()->get(Router::class);
+ foreach ($router->methods as $method) {
+ $router->addRoute($method, $route, $handler);
+ }
+ }
+
+ /**
+ * @param $route
+ * @param $handler
+ * @return void
+ * @throws
+ */
+ public static function delete($route, $handler): void
+ {
+ $router = Kiri::getDi()->get(Router::class);
+ $router->addRoute('DELETE', $route, $handler);
+ }
+
+
+ /**
+ * @param $route
+ * @param $handler
+ * @return void
+ * @throws Exception
+ */
+ public static function head($route, $handler): void
+ {
+ $router = Kiri::getDi()->get(Router::class);
+ $router->addRoute('HEAD', $route, $handler);
+ }
+
+
+ /**
+ * @param $route
+ * @param $handler
+ * @return void
+ * @throws
+ */
+ public static function put($route, $handler): void
+ {
+ $router = Kiri::getDi()->get(Router::class);
+ $router->addRoute('PUT', $route, $handler);
+ }
+
+
+ /**
+ * @param string|array $method
+ * @param string $route
+ * @param string|Closure $closure
+ * @throws \ReflectionException
+ */
+ public function addRoute(string|array $method, string $route, string|Closure $closure)
+ {
+ if (!is_array($method)) $method = [$method];
+ $route = $this->getPath($route);
+ if (is_string($closure)) {
+ $closure = explode('@', $closure);
+ $closure[0] = $this->addNamespace($closure[0]);
+ if (!class_exists($closure[0])) {
+ return;
+ }
+ $this->addMiddlewares(...$closure);
+ }
+ foreach ($method as $value) {
+ HandlerManager::add($route, $value, new Handler($route, $closure));
+ }
+ }
+
+
+ /**
+ * @param array $config
+ * @param Closure $closure
+ */
+ public static function group(array $config, Closure $closure)
+ {
+ $router = Kiri::getDi()->get(Router::class);
+
+ array_push($router->groupTack, $config);
+
+ call_user_func($closure, $router);
+
+ array_pop($router->groupTack);
+ }
+
+
+ /**
+ * @param string $route
+ * @return string
+ */
+ protected function getPath(string $route): string
+ {
+ $route = ltrim($route, '/');
+ $prefix = array_column($this->groupTack, 'prefix');
+ if (empty($prefix = array_filter($prefix))) {
+ return '/' . $route;
+ }
+ return '/' . implode('/', $prefix) . '/' . $route;
+ }
+
+
+ /**
+ * @param $controller
+ * @param $method
+ */
+ protected function addMiddlewares($controller, $method)
+ {
+ $middleware = array_column($this->groupTack, 'middleware');
+ if (empty($middleware = array_filter($middleware))) {
+ return;
+ }
+ foreach ($middleware as $value) {
+ MiddlewareManager::add($controller, $method, $value);
+ }
+ }
+
+
+ /**
+ * @param $class
+ * @return string|null
+ */
+ protected function addNamespace($class): ?string
+ {
+ $middleware = array_column($this->groupTack, 'namespace');
+ if (empty($middleware = array_filter($middleware))) {
+ return $class;
+ }
+ $middleware[] = $class;
+ return implode('\\', array_map(function ($value) {
+ return trim($value, '\\');
+ }, $middleware));
+ }
+
+
+ /**
+ * @throws Exception
+ */
+ public function read_files()
+ {
+ $this->loadRouteDir(APP_PATH . 'routes');
+ }
+
+
+ /**
+ * @param $path
+ * @throws Exception
+ * 加载目录下的路由文件
+ */
+ private function loadRouteDir($path)
+ {
+ $files = glob($path . '/*');
+ for ($i = 0; $i < count($files); $i++) {
+ if (is_dir($files[$i])) {
+ $this->loadRouteDir($files[$i]);
+ } else {
+ $this->loadRouterFile($files[$i]);
+ }
+ }
+ }
+
+
+ /**
+ * @param $files
+ * @throws Exception
+ */
+ private function loadRouterFile($files)
+ {
+ try {
+ include_once "Router.php";
+ } catch (Throwable $exception) {
+ di(Logger::class)->error('router', [
+ $exception->getMessage(),
+ $exception->getFile(),
+ $exception->getLine(),
+ ]);
+ } finally {
+ if (isset($exception)) {
+ unset($exception);
+ }
+ }
+ }
+
+
+}
diff --git a/http-core/Http.php b/http-core/Http.php
new file mode 100644
index 00000000..7b8c6d78
--- /dev/null
+++ b/http-core/Http.php
@@ -0,0 +1,123 @@
+exceptionHandler = Kiri::getDi()->get($exceptionHandler);
+ $this->responseEmitter = Kiri::getDi()->get(ResponseEmitter::class);
+ }
+
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @throws Exception
+ */
+ public function onRequest(Request $request, Response $response): void
+ {
+ try {
+ [$PsrRequest, $PsrResponse] = $this->initRequestResponse($request);
+ /** @var Handler $handler */
+ $handler = HandlerManager::get($request->server['request_uri'], $request->getMethod());
+ if (is_integer($handler)) {
+ $PsrResponse->withStatus($handler)->withBody(new Stream('Allow Method[' . $request->getMethod() . '].'));
+ } else if (is_null($handler)) {
+ $PsrResponse->withStatus(404)->withBody(new Stream('Page not found.'));
+ } else {
+ $PsrResponse = $this->handler($handler, $PsrRequest);
+ }
+ } catch (\Throwable $throwable) {
+ $PsrResponse = $this->exceptionHandler->emit($throwable, $this->response);
+ } finally {
+ $this->responseEmitter->sender($response, $PsrResponse);
+ $this->eventDispatch->dispatch(new OnAfterRequest());
+ }
+ }
+
+
+ /**
+ * @param Handler $handler
+ * @param $PsrRequest
+ * @return ResponseInterface
+ * @throws Exception
+ */
+ protected function handler(Handler $handler, $PsrRequest): \Psr\Http\Message\ResponseInterface
+ {
+ $dispatcher = new Dispatcher($handler, $handler->_middlewares);
+ return $dispatcher->handle($PsrRequest);
+ }
+
+
+ /**
+ * @param Request $request
+ * @return array
+ * @throws Exception
+ */
+ private function initRequestResponse(Request $request): array
+ {
+ $PsrResponse = Context::setContext(ResponseInterface::class, new \Http\Message\Response());
+
+ $PsrRequest = Context::setContext(RequestInterface::class, ServerRequest::createServerRequest($request));
+ if ($PsrRequest->isMethod('OPTIONS')) {
+ $request->server['request_uri'] = '/*';
+ }
+ return [$PsrRequest, $PsrResponse];
+ }
+
+
+}
diff --git a/http-core/Message/ContentType.php b/http-core/Message/ContentType.php
new file mode 100644
index 00000000..a3a3ce23
--- /dev/null
+++ b/http-core/Message/ContentType.php
@@ -0,0 +1,372 @@
+version;
+ }
+
+
+ /**
+ * @param $version
+ * @return static
+ */
+ public function withProtocolVersion($version): static
+ {
+ $this->version = $version;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getHeaders(): array
+ {
+ return $this->headers;
+ }
+
+
+ /**
+ * @param $name
+ * @return bool
+ */
+ public function hasHeader($name): bool
+ {
+ return array_key_exists($name, $this->headers);
+ }
+
+
+ /**
+ * @param $name
+ * @return string|array|null
+ */
+ #[Pure] public function getHeader($name): string|null|array
+ {
+ if (!$this->hasHeader($name)) {
+ return null;
+ }
+ return $this->headers[$name];
+ }
+
+
+ /**
+ * @return array
+ */
+ public function parse_curl_header(): array
+ {
+ $_headers = [];
+ foreach ($this->headers as $key => $val) {
+ $_headers[] = $key . ': ' . implode(';', $val);
+ }
+ return $_headers;
+ }
+
+
+ /**
+ * @throws \Exception
+ */
+ public function withData(string $headerString): static
+ {
+ [$headers, $body] = explode("\r\n\r\n", $headerString);
+
+ $this->stream = new Stream($body);
+
+ return $this->slip_headers($headers);
+ }
+
+
+ /**
+ * @param $headers
+ * @return $this
+ * @throws \Exception
+ */
+ private function slip_headers($headers): static
+ {
+ $headers = explode("\r\n", $headers);
+
+ $this->resolve_status(array_shift($headers));
+
+ foreach ($headers as $header) {
+ [$key, $value] = explode(': ', $header);
+ $this->withHeader($key, $value);
+ }
+ return $this;
+ }
+
+
+ /**
+ * @param string $protocol
+ */
+ private function resolve_status(string $protocol)
+ {
+ if ($this instanceof ResponseInterface) {
+ [$sch, $status, $message] = explode(' ', $protocol);
+ [$sch, $protocolVersion] = explode('/', $sch);
+ $this->withProtocolVersion($protocolVersion)
+ ->withStatus(intval($status));
+ }
+ }
+
+
+ /**
+ * @param $key
+ * @param $value
+ */
+ private function addRequestHeader($key, $value)
+ {
+ $this->headers[$key] = [$value];
+ }
+
+
+ /**
+ * @param $name
+ * @return string|null
+ */
+ #[Pure] public function getHeaderLine($name): string|null
+ {
+ if ($this->hasHeader($name)) {
+ return implode(';', $this->headers[$name]);
+ }
+ return null;
+ }
+
+
+ /**
+ * @return string|null
+ */
+ #[Pure] public function getContentType(): ?string
+ {
+ return $this->getHeaderLine('Content-Type');
+ }
+
+
+ /**
+ * @param $name
+ * @param $value
+ * @return static
+ */
+ public function withHeader($name, $value): static
+ {
+ if (!is_array($value)) {
+ $value = [$value];
+ }
+ $this->headers[$name] = $value;
+ return $this;
+ }
+
+
+ /**
+ * @param array $headers
+ * @return static
+ */
+ public function withHeaders(array $headers): static
+ {
+ $this->headers = $headers;
+ return $this;
+ }
+
+
+ /**
+ * @param $name
+ * @param $value
+ * @return static
+ * @throws
+ */
+ public function withAddedHeader($name, $value): static
+ {
+ if (!array_key_exists($name, $this->headers)) {
+ throw new \Exception('Headers `' . $name . '` not exists.');
+ }
+ $this->headers[$name][] = $value;
+ return $this;
+ }
+
+
+ /**
+ * @param $name
+ * @return static
+ */
+ public function withoutHeader($name): static
+ {
+ unset($this->headers[$name]);
+ return $this;
+ }
+
+
+ /**
+ * @return null|array
+ */
+ public function getCookieParams(): ?array
+ {
+ return $this->cookieParams;
+ }
+
+
+ /**
+ * @param array|null $cookies
+ * @return static
+ */
+ public function withCookieParams(?array $cookies): static
+ {
+ $this->cookieParams = $cookies;
+ return $this;
+ }
+
+ /**
+ * @return StreamInterface
+ */
+ public function getBody(): StreamInterface
+ {
+ return $this->stream;
+ }
+
+
+ /**
+ * @param StreamInterface $body
+ * @return static
+ */
+ public function withBody(StreamInterface $body): static
+ {
+ $this->stream = $body;
+ return $this;
+ }
+
+
+ /**
+ * @return string|null
+ */
+ #[Pure] public function getAccessControlAllowOrigin(): ?string
+ {
+ return $this->getHeaderLine('Access-Control-Allow-Origin');
+ }
+
+
+ /**
+ * @return string|null
+ */
+ #[Pure] public function getAccessControlAllowHeaders(): ?string
+ {
+ return $this->getHeaderLine('Access-Control-Allow-Headers');
+ }
+
+
+ /**
+ * @return string|null
+ */
+ #[Pure] public function getAccessControlRequestMethod(): ?string
+ {
+ return $this->getHeaderLine('Access-Control-Request-Method');
+ }
+
+
+ protected function setStore($key, callable $callback)
+ {
+
+ }
+
+}
diff --git a/http-core/Message/OnDownload.php b/http-core/Message/OnDownload.php
new file mode 100644
index 00000000..f7d749e8
--- /dev/null
+++ b/http-core/Message/OnDownload.php
@@ -0,0 +1,104 @@
+ 'image/png',
+ 'jpeg' => 'image/jpeg',
+ 'gif' => 'image/gif',
+ 'bmp' => 'image/bmp',
+ 'ico' => 'image/vnd.microsoft.icon',
+ 'tiff' => 'image/tiff',
+ 'svg' => 'image/svg+xml',
+ ];
+
+
+ /**
+ * @param string $path
+ * @param false $isChunk
+ * @param int $size
+ * @param int $offset
+ * @return $this
+ */
+ public function path(string $path, bool $isChunk = false, int $size = -1, int $offset = 0): OnDownload
+ {
+ $this->path = $path;
+ $this->isChunk = $isChunk;
+ $this->size = $size;
+ $this->offset = $offset;
+ return $this->emitter();
+ }
+
+
+ /**
+ * @return $this
+ */
+ public function emitter(): static
+ {
+ $explode = explode('/', $this->path);
+ $this->withHeader('Pragma', 'public');
+ $this->withHeader('Expires', '0');
+ $this->withHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0');
+ $this->withHeader('Content-Disposition', 'attachment;filename=' . end($explode));
+ $this->withHeader('Content-Type', $type = get_file_extension($this->path));
+ if (!in_array($type, self::IMAGES)) {
+ $this->withHeader('Content-Transfer-Encoding', 'binary');
+ }
+ if ($this->isChunk) {
+ $resource = fopen($this->path, 'r');
+
+ $state = fstat($resource);
+
+ $this->withHeader('Content-length', $state['size']);
+ }
+ return $this;
+ }
+
+
+ /**
+ * @param \Swoole\Http\Response $response
+ */
+ public function dispatch(mixed $response)
+ {
+ if (!$this->isChunk) {
+ $response->sendfile($this->path);
+ } else {
+ $this->chunk($response);
+ }
+ }
+
+
+ /**
+ * @param \Swoole\Http\Response $response
+ */
+ private function chunk(\Swoole\Http\Response $response): void
+ {
+ $resource = fopen($this->path, 'r');
+
+ $state = fstat($resource);
+
+ $offset = $this->offset;
+ while ($file = fread($resource, $this->size)) {
+ $response->write($file);
+ fseek($resource, $offset);
+ if ($offset >= $state['size']) {
+ break;
+ }
+ $offset += $this->size;
+ }
+ $response->end();
+ }
+}
diff --git a/http-core/Message/Parse.php b/http-core/Message/Parse.php
new file mode 100644
index 00000000..90b91274
--- /dev/null
+++ b/http-core/Message/Parse.php
@@ -0,0 +1,35 @@
+ Xml::toArray($content),
+ '[', '{' => json_decode($content, true),
+ default => call_user_func(function () use ($content) {
+ parse_str($content, $array);
+ return $array;
+ })
+ };
+ }
+
+}
diff --git a/http-core/Message/Request.php b/http-core/Message/Request.php
new file mode 100644
index 00000000..7f198052
--- /dev/null
+++ b/http-core/Message/Request.php
@@ -0,0 +1,115 @@
+authority = $authIdentity;
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getRequestTarget(): string
+ {
+ throw new BadMethodCallException('Not Accomplish Method.');
+ }
+
+
+ /**
+ * @param mixed $requestTarget
+ * @return static
+ */
+ public function withRequestTarget($requestTarget): static
+ {
+ throw new BadMethodCallException('Not Accomplish Method.');
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getMethod(): string
+ {
+ return $this->method;
+ }
+
+
+ /**
+ * @param string $method
+ * @return RequestInterface
+ */
+ public function withMethod($method): RequestInterface
+ {
+ $this->method = $method;
+ return $this;
+ }
+
+
+ /**
+ * @param string $method
+ * @return bool
+ */
+ public function isMethod(string $method): bool
+ {
+ return $this->method == $method;
+ }
+
+
+ /**
+ * @return UriInterface
+ */
+ public function getUri(): UriInterface
+ {
+ return $this->uriInterface;
+ }
+
+
+ /**
+ * @param UriInterface $uri
+ * @param false $preserveHost
+ * @return $this|Request
+ */
+ public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
+ {
+ $this->uriInterface = $uri;
+ return $this;
+ }
+}
diff --git a/http-core/Message/Response.php b/http-core/Message/Response.php
new file mode 100644
index 00000000..071a5434
--- /dev/null
+++ b/http-core/Message/Response.php
@@ -0,0 +1,221 @@
+stream = new Stream();
+ }
+
+
+ /**
+ * @return int
+ */
+ public function getStatusCode(): int
+ {
+ return $this->statusCode;
+ }
+
+
+ /**
+ * @param int $code
+ * @param string $reasonPhrase
+ * @return $this|Response
+ */
+ public function withStatus($code, $reasonPhrase = ''): static
+ {
+ $this->statusCode = $code;
+ $this->reasonPhrase = $reasonPhrase;
+ return $this;
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getReasonPhrase(): string
+ {
+ return $this->reasonPhrase;
+ }
+
+
+ /**
+ * @return string|null
+ */
+ #[Pure] public function getAccessControlAllowOrigin(): ?string
+ {
+ return $this->getHeaderLine('Access-Control-Allow-Origin');
+ }
+
+
+ /**
+ * @return string|null
+ */
+ #[Pure] public function getAccessControlAllowHeaders(): ?string
+ {
+ return $this->getHeaderLine('Access-Control-Allow-Headers');
+ }
+
+
+ /**
+ * @return string|null
+ */
+ #[Pure] public function getAccessControlRequestMethod(): ?string
+ {
+ return $this->getHeaderLine('Access-Control-Request-Method');
+ }
+
+
+ /**
+ * @param string $type
+ * @return Response
+ */
+ public function withContentType(string $type): static
+ {
+ return $this->withHeader('Content-Type', $type);
+ }
+
+
+ /**
+ * @return bool
+ */
+ #[Pure] public function hasContentType(): bool
+ {
+ return $this->hasHeader('Content-Type');
+ }
+
+ /**
+ * @param string|null $value
+ * @return Response
+ */
+ public function withAccessControlAllowHeaders(?string $value): static
+ {
+ return $this->withHeader('Access-Control-Allow-Headers', $value);
+ }
+
+
+ /**
+ * @param string|null $value
+ * @return Response
+ */
+ public function withAccessControlRequestMethod(?string $value): static
+ {
+ return $this->withHeader('Access-Control-Request-Method', $value);
+ }
+
+
+ /**
+ * @param string|null $value
+ * @return Response
+ */
+ public function withAccessControlAllowOrigin(?string $value): static
+ {
+ return $this->withHeader('Access-Control-Allow-Origin', $value);
+ }
+
+
+ /**
+ * @param $data
+ * @param string $contentType
+ * @return static
+ */
+ public function json($data, string $contentType = 'application/json'): static
+ {
+ $this->stream->write(json_encode($data));
+
+ return $this->withContentType($contentType);
+ }
+
+
+ /**
+ * @param $data
+ * @param string $contentType
+ * @return static
+ */
+ public function html($data, string $contentType = 'text/html'): static
+ {
+ if (!is_string($data)) {
+ $data = json_encode($data);
+ }
+
+ $this->stream->write((string)$data);
+
+ return $this->withContentType($contentType);
+ }
+
+
+ /**
+ * @param $data
+ * @param string $contentType
+ * @return static
+ */
+ public function xml($data, string $contentType = 'application/xml'): static
+ {
+ $this->stream->write(Help::toXml($data));
+
+ return $this->withContentType($contentType);
+ }
+
+
+ /**
+ * @param string $charset
+ * @return $this
+ */
+ public function withCharset(string $charset): static
+ {
+ $type = explode('charset', $this->getContentType())[0];
+ $this->withContentType(
+ rtrim($type,';') . ';charset=' . $charset
+ );
+ return $this;
+ }
+
+
+ /**
+ * @param $path
+ * @param bool $isChunk
+ * @param int $size
+ * @param int $offset
+ * @return OnDownloadInterface
+ * @throws Exception
+ */
+ public function file($path, bool $isChunk = false, int $size = -1, int $offset = 0): OnDownloadInterface
+ {
+ $path = realpath($path);
+ if (!file_exists($path) || !is_readable($path)) {
+ throw new Exception('Cannot read file "' . $path . '", no permission');
+ }
+ return (new OnDownload())->path($path, $isChunk, $size, $offset);
+ }
+}
diff --git a/http-core/Message/ServerRequest.php b/http-core/Message/ServerRequest.php
new file mode 100644
index 00000000..46b7076a
--- /dev/null
+++ b/http-core/Message/ServerRequest.php
@@ -0,0 +1,208 @@
+serverParams = $server;
+ return $this;
+ }
+
+ /**
+ * @param \Swoole\Http\Request $server
+ * @return static
+ */
+ public function withServerTarget(\Swoole\Http\Request $server): static
+ {
+ $this->serverTarget = $server;
+ return $this;
+ }
+
+
+ /**
+ * @param \Swoole\Http\Request $request
+ * @return static|ServerRequestInterface
+ * @throws \Exception
+ */
+ public static function createServerRequest(\Swoole\Http\Request $request): static|ServerRequestInterface
+ {
+ $serverRequest = new ServerRequest();
+ $serverRequest->withData($request->getData());
+ $serverRequest->withServerParams($request->server);
+ $serverRequest->withServerTarget($request);
+ $serverRequest->withCookieParams($request->cookie);
+ $serverRequest->withUri(Uri::parseUri($request));
+ $serverRequest->withQueryParams($request->get ?? []);
+ $serverRequest->withUploadedFiles($request->files ?? []);
+ $serverRequest->withMethod($request->getMethod());
+ $serverRequest->withParsedBody($request->post);
+ return $serverRequest;
+ }
+
+
+ /**
+ * @return null|array
+ */
+ public function getServerParams(): ?array
+ {
+ return $this->serverParams;
+ }
+
+
+ /**
+ * @return array|null
+ */
+ public function getQueryParams(): ?array
+ {
+ return $this->queryParams;
+ }
+
+
+ /**
+ * @param array $query
+ * @return ServerRequestInterface
+ */
+ public function withQueryParams(array $query): ServerRequestInterface
+ {
+ $this->queryParams = $query;
+ return $this;
+ }
+
+
+ /**
+ * @return array|null
+ */
+ public function getUploadedFiles(): ?array
+ {
+ return $this->uploadedFiles;
+ }
+
+
+ /**
+ * @param array $uploadedFiles
+ * @return ServerRequestInterface
+ */
+ public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface
+ {
+ $this->uploadedFiles = $uploadedFiles;
+ return $this;
+ }
+
+
+ /**
+ * @return array|object|null
+ */
+ public function getParsedBody(): object|array|null
+ {
+ if (empty($this->parsedBody)) {
+ $callback = Context::getContext(self::PARSE_BODY);
+
+ $this->parsedBody = $callback($this->getBody(), $this->serverTarget->post);
+ }
+ return $this->parsedBody;
+ }
+
+
+ /**
+ * @param array|object|null $data
+ * @return ServerRequestInterface
+ */
+ public function withParsedBody($data): ServerRequestInterface
+ {
+ $functions = function (StreamInterface $stream) use ($data) {
+ $content = Parse::data($stream->getContents());
+ if (!empty($content)) {
+ return $content;
+ }
+ return $data;
+ };
+ Context::setContext(self::PARSE_BODY, $functions);
+ return $this;
+ }
+
+
+ /**
+ * @return array
+ */
+ public function getAttributes(): array
+ {
+ throw new \BadMethodCallException('Not Accomplish Method.');
+ }
+
+
+ /**
+ * @param string $name
+ * @param null $default
+ * @return mixed
+ */
+ public function getAttribute($name, $default = null): mixed
+ {
+ throw new \BadMethodCallException('Not Accomplish Method.');
+ }
+
+
+ /**
+ * @param string $name
+ * @param mixed $value
+ * @return ServerRequestInterface
+ */
+ public function withAttribute($name, $value): ServerRequestInterface
+ {
+ throw new \BadMethodCallException('Not Accomplish Method.');
+ }
+
+
+ /**
+ * @param string $name
+ * @return ServerRequestInterface
+ */
+ public function withoutAttribute($name): ServerRequestInterface
+ {
+ throw new \BadMethodCallException('Not Accomplish Method.');
+ }
+}
diff --git a/http-core/Message/StatusCode.php b/http-core/Message/StatusCode.php
new file mode 100644
index 00000000..858168f4
--- /dev/null
+++ b/http-core/Message/StatusCode.php
@@ -0,0 +1,93 @@
+ 'Continue 初始的请求已经接受,客户应当继续发送请求的其余部分。(HTTP 1.1新)',
+ self::CODE_101 => 'Switching Protocols 服务器将遵从客户的请求转换到另外一种协议(HTTP 1.1新)',
+ self::CODE_200 => '(成功) 服务器已成功处理了请求。 通常,这表示服务器提供了请求的网页。',
+ self::CODE_201 => '(已创建) 请求成功并且服务器创建了新的资源。',
+ self::CODE_202 => '(已接受) 服务器已接受请求,但尚未处理。',
+ self::CODE_203 => '(非授权信息) 服务器已成功处理了请求,但返回的信息可能来自另一来源。',
+ self::CODE_204 => '(无内容) 服务器成功处理了请求,但没有返回任何内容。',
+ self::CODE_205 => '(重置内容) 服务器成功处理了请求,但没有返回任何内容。',
+ self::CODE_206 => '(部分内容) 服务器成功处理了部分 GET 请求。',
+ self::CODE_300 => '(多种选择) 针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。',
+ self::CODE_301 => '(永久移动) 请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。',
+ self::CODE_302 => '(临时移动) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。',
+ self::CODE_303 => '(查看其他位置) 请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。',
+ self::CODE_304 => '(未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。',
+ self::CODE_305 => '(使用代理) 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理。',
+ self::CODE_307 => '(临时重定向) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。',
+ self::CODE_400 => '(错误请求) 服务器不理解请求的语法。',
+ self::CODE_401 => '(未授权) 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。',
+ self::CODE_403 => '(禁止) 服务器拒绝请求。',
+ self::CODE_404 => '(未找到) 服务器找不到请求的网页。',
+ self::CODE_405 => '(方法禁用) 禁用请求中指定的方法。',
+ self::CODE_406 => '(不接受) 无法使用请求的内容特性响应请求的网页。',
+ self::CODE_407 => '(需要代理授权) 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理。',
+ self::CODE_408 => '(请求超时) 服务器等候请求时发生超时。',
+ self::CODE_409 => '(冲突) 服务器在完成请求时发生冲突。 服务器必须在响应中包含有关冲突的信息。',
+ self::CODE_410 => '(已删除) 如果请求的资源已永久删除,服务器就会返回此响应。',
+ self::CODE_411 => '(需要有效长度) 服务器不接受不含有效内容长度标头字段的请求。',
+ self::CODE_412 => '(未满足前提条件) 服务器未满足请求者在请求中设置的其中一个前提条件。',
+ self::CODE_413 => '(请求实体过大) 服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。',
+ self::CODE_414 => '(请求的 URI 过长) 请求的 URI(通常为网址)过长,服务器无法处理。',
+ self::CODE_415 => '(不支持的媒体类型) 请求的格式不受请求页面的支持。',
+ self::CODE_416 => '(请求范围不符合要求) 如果页面无法提供请求的范围,则服务器会返回此状态代码。',
+ self::CODE_417 => '(未满足期望值) 服务器未满足"期望"请求标头字段的要求。',
+ self::CODE_423 => ' 锁定的错误。',
+ self::CODE_500 => '(服务器内部错误) 服务器遇到错误,无法完成请求。',
+ self::CODE_501 => '(尚未实施) 服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码。',
+ self::CODE_502 => '(错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。',
+ self::CODE_503 => '(服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。',
+ self::CODE_504 => '(网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。',
+ self::CODE_505 => '(HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。',
+ ];
+
+}
diff --git a/http-core/Message/Stream.php b/http-core/Message/Stream.php
new file mode 100644
index 00000000..11bd1a92
--- /dev/null
+++ b/http-core/Message/Stream.php
@@ -0,0 +1,237 @@
+content = $stream;
+ if (!is_resource($stream)) {
+ $this->size = strlen($stream);
+ } else {
+ $state = fstat($this->content);
+ if ($state) {
+ $this->size = $state['size'];
+ }
+ }
+ }
+
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->content;
+ }
+
+
+ /**
+ *
+ */
+ public function close()
+ {
+ throw new \BadMethodCallException('Not Accomplish Method.');
+ }
+
+
+ /**
+ * @return resource|null
+ */
+ public function detach()
+ {
+ if (!is_resource($this->content)) {
+ throw new \BadMethodCallException('Not Accomplish Method.');
+ }
+ $steam = stream_context_create();
+ stream_copy_to_stream($this->content, $steam);
+ return $steam;
+ }
+
+
+ /**
+ * @return int
+ */
+ public function getSize(): int
+ {
+ return $this->size;
+ }
+
+
+ /**
+ * @return bool|int
+ */
+ public function tell(): bool|int
+ {
+ if (!is_resource($this->content)) {
+ throw new \BadMethodCallException('Not Accomplish Method.');
+ }
+ return ftell($this->content);
+ }
+
+
+ /**
+ * @return bool
+ */
+ public function eof(): bool
+ {
+ throw new \BadMethodCallException('Not Accomplish Method.');
+ }
+
+
+ /**
+ * @return bool
+ */
+ public function isSeekable(): bool
+ {
+ throw new \BadMethodCallException('Not Accomplish Method.');
+ }
+
+
+ /**
+ * @param int $offset
+ * @param int $whence
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if (!is_resource($this->content)) {
+ throw new \BadMethodCallException('Not Accomplish Method.');
+ }
+ fseek($this->content, $offset, $whence);
+ }
+
+
+ /**
+ *
+ */
+ public function rewind()
+ {
+ throw new \BadMethodCallException('Not Accomplish Method.');
+ }
+
+
+ /**
+ * @return bool
+ */
+ public function isWritable(): bool
+ {
+ if (!is_resource($this->content)) {
+ return true;
+ }
+ if (is_writable($this->content)) {
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * @param string $string
+ * @return int
+ */
+ public function write($string): int
+ {
+ if (is_resource($this->content)) {
+ fwrite($this->content, $string);
+ $state = fstat($this->content);
+ if ($state) {
+ $this->size = $state['size'];
+ }
+ } else {
+ $this->content = $string;
+ $this->size = strlen($string);
+ }
+ return $this->size;
+ }
+
+
+ /**
+ * @return bool
+ */
+ public function isReadable(): bool
+ {
+ if (!is_resource($this->content)) {
+ return true;
+ }
+ if (is_readable($this->content)) {
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * @param int $length
+ * @return false|string
+ */
+ public function read($length): bool|string
+ {
+ if (is_resource($this->content)) {
+ return fread($this->content, $length);
+ } else {
+ return $this->content;
+ }
+ }
+
+
+ /**
+ * @return string|bool
+ */
+ public function getContents(): string|bool
+ {
+ if (is_resource($this->content)) {
+ return stream_get_contents($this->content);
+ } else {
+ return $this->content;
+ }
+ }
+
+
+ /**
+ * @param null $key
+ * @return array
+ */
+ #[ArrayShape([
+ "timed_out" => "bool",
+ "blocked" => "bool",
+ "eof" => "bool",
+ "unread_bytes" => "int",
+ "stream_type" => "string",
+ "wrapper_type" => "string",
+ "wrapper_data" => "mixed",
+ "mode" => "string",
+ "seekable" => "bool",
+ "uri" => "string",
+ "crypto" => "array",
+ "mediatype" => "string",
+ ])]
+ public function getMetadata($key = null): array
+ {
+ if (is_resource($this->content)) {
+ return stream_get_meta_data($this->content);
+ }
+ throw new \BadMethodCallException('Not Accomplish Method.');
+ }
+}
diff --git a/http-core/Message/Uploaded.php b/http-core/Message/Uploaded.php
new file mode 100644
index 00000000..4d434ee4
--- /dev/null
+++ b/http-core/Message/Uploaded.php
@@ -0,0 +1,118 @@
+ "There is no error, the file uploaded with success",
+ 1 => "The uploaded file exceeds the upload_max_filesize directive in php.ini",
+ 2 => "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form",
+ 3 => "The uploaded file was only partially uploaded",
+ 4 => "No file was uploaded",
+ 6 => "Missing a temporary folder"
+ ];
+
+
+ /**
+ * @var resource
+ */
+ private mixed $stream;
+
+
+ /**
+ * @param string $tmp_name
+ * @param string $name
+ * @param string $type
+ * @param int $size
+ * @param int $error
+ */
+ public function __construct(
+ public string $tmp_name,
+ public string $name,
+ public string $type,
+ public int $size,
+ public int $error
+ )
+ {
+ }
+
+
+ /**
+ * @return StreamInterface
+ * @throws Exception
+ */
+ public function getStream(): StreamInterface
+ {
+ if ($this->stream instanceof Stream) {
+ return $this->stream;
+ }
+
+ $this->stream = new Stream(fopen($this->tmp_name, 'r+'));
+
+ return $this->stream;
+ }
+
+
+ /**
+ * @param string $targetPath
+ * @return StreamInterface
+ * @throws Exception
+ */
+ public function moveTo($targetPath): StreamInterface
+ {
+ @move_uploaded_file($this->tmp_name, $targetPath);
+ if (!file_exists($targetPath)) {
+ throw new Exception('File save fail.');
+ }
+
+ if ($this->stream instanceof Stream) {
+ $this->stream->close();
+ $this->stream = null;
+ }
+
+ $this->tmp_name = $targetPath;
+ return $this->getStream();
+ }
+
+
+ /**
+ * @return int
+ */
+ public function getSize(): int
+ {
+ return $this->size;
+ }
+
+ /**
+ * @return int
+ */
+ public function getError(): int
+ {
+ return $this->error;
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getClientFilename(): string
+ {
+ return $this->name;
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getClientMediaType(): string
+ {
+ return $this->type;
+ }
+}
diff --git a/http-core/Message/Uri.php b/http-core/Message/Uri.php
new file mode 100644
index 00000000..68e610a0
--- /dev/null
+++ b/http-core/Message/Uri.php
@@ -0,0 +1,256 @@
+scheme;
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getAuthority(): string
+ {
+ throw new \BadMethodCallException('Not Accomplish Method.');
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getUserInfo(): string
+ {
+ throw new \BadMethodCallException('Not Accomplish Method.');
+ }
+
+ /**
+ * @return string
+ */
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+
+ /**
+ * @return int|null
+ */
+ public function getPort(): ?int
+ {
+ return $this->port;
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getPath(): string
+ {
+ return $this->path;
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getQuery(): string
+ {
+ return $this->queryString;
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getFragment(): string
+ {
+ throw new \BadMethodCallException('Not Accomplish Method.');
+ }
+
+
+ /**
+ * @param string $scheme
+ * @return $this|Uri
+ */
+ public function withScheme($scheme): UriInterface
+ {
+ $this->scheme = $scheme;
+ return $this;
+ }
+
+
+ /**
+ * @param string $user
+ * @param null $password
+ * @return Uri
+ */
+ public function withUserInfo($user, $password = null): UriInterface
+ {
+ throw new \BadMethodCallException('Not Accomplish Method.');
+ }
+
+
+ /**
+ * @param string $host
+ * @return $this|Uri
+ */
+ public function withHost($host): UriInterface
+ {
+ $this->host = $host;
+ return $this;
+ }
+
+
+ /**
+ * @param int|null $port
+ * @return $this|Uri
+ */
+ public function withPort($port): UriInterface
+ {
+ $this->port = $port;
+ return $this;
+ }
+
+
+ /**
+ * @param string $path
+ * @return $this|Uri
+ */
+ public function withPath($path): UriInterface
+ {
+ $this->path = $path;
+ return $this;
+ }
+
+
+ /**
+ * @param string $query
+ * @return $this|Uri
+ */
+ public function withQuery($query): UriInterface
+ {
+ $this->queryString = $query;
+ return $this;
+ }
+
+
+ /**
+ * @param string $fragment
+ * @return Uri
+ */
+ public function withFragment($fragment): UriInterface
+ {
+ throw new \BadMethodCallException('Not Accomplish Method.');
+ }
+
+
+ /**
+ * @return string
+ */
+ public function __toString(): string
+ {
+ $domain = sprintf('%s://%s', $this->scheme, $this->host);
+ if (!in_array($this->port, [80, 443])) {
+ $domain .= ':' . $this->port;
+ }
+ if (empty($this->query) && empty($this->fragment)) {
+ return $domain . $this->path;
+ }
+ return sprintf('%s?%s#%s', $domain . $this->path,
+ $this->queryString, $this->fragment);
+ }
+
+
+ /**
+ * @return int
+ */
+ public function getDefaultPort(): int
+ {
+ return $this->scheme == 'https' ? 443 : 80;
+ }
+
+
+ /**
+ * @param Request $request
+ * @return UriInterface
+ */
+ public static function parseUri(Request $request): UriInterface
+ {
+ $server = $request->server;
+ $header = $request->header;
+ $uri = new static();
+ $uri = $uri->withScheme(!empty($server['https']) && $server['https'] !== 'off' ? 'https' : 'http');
+ if (isset($request->header['x-forwarded-proto'])) {
+ $uri->withScheme($request->header['x-forwarded-proto'])->withPort(443);
+ }
+
+ $hasPort = false;
+ if (isset($server['http_host'])) {
+ $hostHeaderParts = explode(':', $server['http_host']);
+ $uri = $uri->withHost($hostHeaderParts[0]);
+ if (isset($hostHeaderParts[1])) {
+ $hasPort = true;
+ $uri = $uri->withPort($hostHeaderParts[1]);
+ }
+ } elseif (isset($server['server_name'])) {
+ $uri = $uri->withHost($server['server_name']);
+ } elseif (isset($server['server_addr'])) {
+ $uri = $uri->withHost($server['server_addr']);
+ } elseif (isset($header['host'])) {
+ $hasPort = true;
+ if (strpos($header['host'], ':')) {
+ [$host, $port] = explode(':', $header['host'], 2);
+ if ($port != $uri->getDefaultPort()) {
+ $uri = $uri->withPort($port);
+ }
+ } else {
+ $host = $header['host'];
+ }
+
+ $uri = $uri->withHost($host);
+ }
+
+ if (!$hasPort && isset($server['server_port'])) {
+ $uri = $uri->withPort($server['server_port']);
+ }
+
+ $hasQuery = false;
+ if (isset($server['request_uri'])) {
+ $requestUriParts = explode('?', $server['request_uri']);
+ $uri = $uri->withPath($requestUriParts[0]);
+ if (isset($requestUriParts[1])) {
+ $hasQuery = true;
+ $uri = $uri->withQuery($requestUriParts[1]);
+ }
+ }
+
+ if (!$hasQuery && isset($server['query_string'])) {
+ $uri = $uri->withQuery($server['query_string']);
+ }
+
+ return $uri;
+ }
+}
diff --git a/http-core/OnDownloadInterface.php b/http-core/OnDownloadInterface.php
new file mode 100644
index 00000000..6fd1ca3f
--- /dev/null
+++ b/http-core/OnDownloadInterface.php
@@ -0,0 +1,12 @@
+