diff --git a/http-message/Message.php b/http-message/Message.php new file mode 100644 index 00000000..1338df4c --- /dev/null +++ b/http-message/Message.php @@ -0,0 +1,195 @@ +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]; + } + + + /** + * @param \Swoole\Http\Request $request + * @return static + */ + public function parseRequestHeaders(\Swoole\Http\Request $request): static + { + $index = strpos($request->getData(), "\r\n\r\n"); + $headers = explode("\r\n", substr($request->getData(), 0, $index)); + array_shift($headers); + foreach ($headers as $header) { + [$key, $value] = explode(': ', $header); + $this->addRequestHeader($key, $value); + } + return $this; + } + + + /** + * @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 $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 string + */ + public function getBody(): string + { + return $this->stream->getContents(); + } + + + /** + * @param StreamInterface $body + * @return static + */ + public function withBody(StreamInterface $body): static + { + $this->stream = $body; + return $this; + } + +} diff --git a/http-message/Parse.php b/http-message/Parse.php new file mode 100644 index 00000000..6692e4ce --- /dev/null +++ b/http-message/Parse.php @@ -0,0 +1,34 @@ +method; } @@ -53,7 +63,6 @@ class TRequest implements RequestInterface */ public function withMethod($method): RequestInterface { - // TODO: Implement withMethod() method. $this->method = $method; return $this; } @@ -64,19 +73,18 @@ class TRequest implements RequestInterface */ public function getUri(): UriInterface { - // TODO: Implement getUri() method. - return $this->uri; + return $this->uriInterface; } /** * @param UriInterface $uri * @param false $preserveHost - * @return $this|TRequest + * @return $this|Request */ public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface { - $this->uri = $uri; + $this->uriInterface = $uri; return $this; } } diff --git a/http-message/Response.php b/http-message/Response.php new file mode 100644 index 00000000..f4d8e0ce --- /dev/null +++ b/http-message/Response.php @@ -0,0 +1,78 @@ +statusCode; + } + + + /** + * @param int $code + * @param string $reasonPhrase + * @return $this|\Protocol\Message\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'); + } +} diff --git a/http-message/ServerRequest.php b/http-message/ServerRequest.php new file mode 100644 index 00000000..73fe4dd6 --- /dev/null +++ b/http-message/ServerRequest.php @@ -0,0 +1,270 @@ +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 + */ + public static function createServerRequest(\Swoole\Http\Request $request): static + { + return (new static())->withServerParams($request->server) + ->withServerTarget($request) + ->withCookieParams($request->cookie) + ->withUri(Uri::parseUri($request)) + ->withQueryParams($request->get ?? []) + ->withUploadedFiles($request->files ?? []) + ->withMethod($request->getMethod()) + ->parseRequestHeaders($request) + ->withParsedBody(function (StreamInterface $stream, ?array $posts) { + try { + $content = $stream->getContents(); + if (!empty($content)) { + return Parse::data($content, $this->getContentType()); + } + return $posts; + } catch (\Throwable $throwable) { + return $posts; + } + }); + } + + + /** + * @param string $name + * @param mixed|null $default + * @return mixed + */ + public function post(string $name, mixed $default = null): mixed + { + return $this->parsedBody[$name] ?? $default; + } + + + /** + * @param string $name + * @param string|null $default + * @return string|null + */ + public function query(string $name, ?string $default = null): ?string + { + return $this->queryParams[$name] ?? $default; + } + + + /** + * @param string $name + * @return \Psr\Http\Message\UploadedFileInterface|null + */ + public function file(string $name): ?UploadedFileInterface + { + if (isset($this->parsedBody[$name])) { + $files = $this->parsedBody[$name]; + return new Uploaded($files['tmp_name'], $files['name'], $files['type'], $files['size'], $files['error']); + } + return null; + } + + + /** + * @return null|array + */ + public function getServerParams(): ?array + { + return $this->serverParams; + } + + + /** + * @return null|array + */ + public function getCookieParams(): ?array + { + return $this->cookieParams; + } + + + /** + * @param array $cookies + * @return $this|ServerRequest + */ + public function withCookieParams(array $cookies): ServerRequestInterface + { + $this->cookieParams = $cookies; + return $this; + } + + + /** + * @return array|null + */ + public function getQueryParams(): ?array + { + return $this->queryParams; + } + + + /** + * @param array $query + * @return \Psr\Http\Message\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 \Psr\Http\Message\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('with.parsed.body.callback'); + + $this->parsedBody = $callback($this->getBody()); + } + return $this->parsedBody; + } + + + /** + * @param array|object|null $data + * @return ServerRequestInterface + */ + public function withParsedBody($data): ServerRequestInterface + { + Context::setContext('with.parsed.body.callback', $data); + 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-message/Stream.php b/http-message/Stream.php new file mode 100644 index 00000000..bdac939b --- /dev/null +++ b/http-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-message/Uploaded.php b/http-message/Uploaded.php new file mode 100644 index 00000000..010a4eb8 --- /dev/null +++ b/http-message/Uploaded.php @@ -0,0 +1,102 @@ + "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" + ]; + + + /** + * @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 (!fopen($this->tmp_name, 'r')) { + throw new \Exception('The file "' . $this->name . '" con\'t readable.'); + } + return new Stream(fopen($this->tmp_name, 'r')); + } + + + /** + * @param string $targetPath + * @return \Psr\Http\Message\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.'); + } + $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-message/Uri.php b/http-message/Uri.php new file mode 100644 index 00000000..7d53a7a5 --- /dev/null +++ b/http-message/Uri.php @@ -0,0 +1,255 @@ +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|\Protocol\Message\Uri + */ + public function withScheme($scheme): UriInterface + { + $this->scheme = $scheme; + return $this; + } + + + /** + * @param string $user + * @param null $password + * @return \Protocol\Message\Uri + */ + public function withUserInfo($user, $password = null): UriInterface + { + throw new \BadMethodCallException('Not Accomplish Method.'); + } + + + /** + * @param string $host + * @return $this|\Protocol\Message\Uri + */ + public function withHost($host): UriInterface + { + $this->host = $host; + return $this; + } + + + /** + * @param int|null $port + * @return $this|\Protocol\Message\Uri + */ + public function withPort($port): UriInterface + { + $this->port = $port; + return $this; + } + + + /** + * @param string $path + * @return $this|\Protocol\Message\Uri + */ + public function withPath($path): UriInterface + { + $this->path = $path; + return $this; + } + + + /** + * @param string $query + * @return $this|\Protocol\Message\Uri + */ + public function withQuery($query): UriInterface + { + $this->queryString = $query; + return $this; + } + + + /** + * @param string $fragment + * @return \Protocol\Message\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 \Swoole\Http\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-server/Constrict/ServerRequest.php b/http-server/Constrict/ServerRequest.php deleted file mode 100644 index 033034e1..00000000 --- a/http-server/Constrict/ServerRequest.php +++ /dev/null @@ -1,127 +0,0 @@ -cookies; - } - - - /** - * @param array $cookies - * @return $this|ServerRequest - */ - public function withCookieParams(array $cookies): ServerRequestInterface - { - $this->cookies = $cookies; - return $this; - } - - - /** - * @return array - */ - public function getQueryParams(): array - { - return []; - } - - public function withQueryParams(array $query): ServerRequestInterface - { - return $this; - } - - public function getUploadedFiles() - { - // TODO: Implement getUploadedFiles() method. - } - - public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface - { - return $this; - } - - - /** - * @return array|object|null - */ - public function getParsedBody(): object|array|null - { - return null; - } - - - /** - * @param array|object|null $data - * @return ServerRequestInterface - */ - public function withParsedBody($data): ServerRequestInterface - { - return $this; - } - - - /** - * @return array - */ - public function getAttributes(): array - { - return []; - } - - - /** - * @param string $name - * @param null $default - * @return mixed|null - */ - public function getAttribute($name, $default = null) - { - return $default; - } - - - /** - * @param string $name - * @param mixed $value - * @return ServerRequestInterface - */ - public function withAttribute($name, $value): ServerRequestInterface - { - return $this; - } - - - /** - * @param string $name - * @return ServerRequestInterface - */ - public function withoutAttribute($name): ServerRequestInterface - { - // TODO: Implement withoutAttribute() method. - return $this; - } -}