Support swow psr7-plus interface for http-message. (#5828)

This commit is contained in:
李铭昕 2023-06-15 18:15:06 +08:00 committed by GitHub
parent ebbf469e9b
commit 6bdb3863f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 379 additions and 32 deletions

View File

@ -16,6 +16,7 @@
- [ ] Support v2 and v3 for socketio-server. - [ ] Support v2 and v3 for socketio-server.
- [ ] Support [Psr7Plus](https://github.com/swow/psr7-plus). - [ ] Support [Psr7Plus](https://github.com/swow/psr7-plus).
- [#5828](https://github.com/hyperf/hyperf/pull/5828) Support swow psr7-plus interface for `http-message`.
- [x] Support [pest](https://github.com/pestphp/pest). - [x] Support [pest](https://github.com/pestphp/pest).
- [x] Added `hyperf/helper` component. - [x] Added `hyperf/helper` component.
- [#5815](https://github.com/hyperf/hyperf/pull/5815) Added alias as `mysql` for `pdo` in `hyperf/db`. - [#5815](https://github.com/hyperf/hyperf/pull/5815) Added alias as `mysql` for `pdo` in `hyperf/db`.

View File

@ -79,6 +79,7 @@
"smarty/smarty": "^3.1", "smarty/smarty": "^3.1",
"squizlabs/php_codesniffer": "^3.4", "squizlabs/php_codesniffer": "^3.4",
"swoole/ide-helper": "dev-master", "swoole/ide-helper": "dev-master",
"swow/psr7-plus": "^1.0",
"swow/swow": "^1.0", "swow/swow": "^1.0",
"sy-records/think-template": "^2.0", "sy-records/think-template": "^2.0",
"symfony/console": "^5.0|^6.0", "symfony/console": "^5.0|^6.0",

View File

@ -16,7 +16,8 @@
"hyperf/support": "~3.1.0", "hyperf/support": "~3.1.0",
"hyperf/utils": "~3.1.0", "hyperf/utils": "~3.1.0",
"laminas/laminas-mime": "^2.7", "laminas/laminas-mime": "^2.7",
"psr/http-message": "^1.0|^2.0" "psr/http-message": "^1.0|^2.0",
"swow/psr7-plus": "^1.0"
}, },
"suggest": { "suggest": {
"psr/container": "Required to replace RequestParserInterface." "psr/container": "Required to replace RequestParserInterface."

View File

@ -55,7 +55,7 @@ trait MessageTrait
* *
* @param string $version HTTP protocol version * @param string $version HTTP protocol version
*/ */
public function withProtocolVersion($version): static public function withProtocolVersion(mixed $version): static
{ {
if ($this->protocol === $version) { if ($this->protocol === $version) {
return $this; return $this;
@ -100,7 +100,7 @@ trait MessageTrait
* name using a case-insensitive string comparison. Returns false if * name using a case-insensitive string comparison. Returns false if
* no matching header name is found in the message. * no matching header name is found in the message.
*/ */
public function hasHeader($name): bool public function hasHeader(mixed $name): bool
{ {
return isset($this->headerNames[strtolower($name)]); return isset($this->headerNames[strtolower($name)]);
} }
@ -117,7 +117,7 @@ trait MessageTrait
* header. If the header does not appear in the message, this method MUST * header. If the header does not appear in the message, this method MUST
* return an empty array. * return an empty array.
*/ */
public function getHeader($name): array public function getHeader(mixed $name): array
{ {
$name = strtolower($name); $name = strtolower($name);
@ -146,7 +146,7 @@ trait MessageTrait
* concatenated together using a comma. If the header does not appear in * concatenated together using a comma. If the header does not appear in
* the message, this method MUST return an empty string. * the message, this method MUST return an empty string.
*/ */
public function getHeaderLine($name): string public function getHeaderLine(mixed $name): string
{ {
return implode(', ', $this->getHeader($name)); return implode(', ', $this->getHeader($name));
} }
@ -163,7 +163,7 @@ trait MessageTrait
* @param string|string[] $value header value(s) * @param string|string[] $value header value(s)
* @throws InvalidArgumentException for invalid header names or values * @throws InvalidArgumentException for invalid header names or values
*/ */
public function withHeader($name, $value): static public function withHeader(mixed $name, mixed $value): static
{ {
if (! is_array($value)) { if (! is_array($value)) {
$value = [$value]; $value = [$value];
@ -204,7 +204,7 @@ trait MessageTrait
* @param string|string[] $value header value(s) * @param string|string[] $value header value(s)
* @throws InvalidArgumentException for invalid header names or values * @throws InvalidArgumentException for invalid header names or values
*/ */
public function withAddedHeader($name, $value): static public function withAddedHeader(mixed $name, mixed $value): static
{ {
if (! is_array($value)) { if (! is_array($value)) {
$value = [$value]; $value = [$value];
@ -234,7 +234,7 @@ trait MessageTrait
* *
* @param string $name case-insensitive header field name to remove * @param string $name case-insensitive header field name to remove
*/ */
public function withoutHeader($name): static public function withoutHeader(mixed $name): static
{ {
$normalized = strtolower($name); $normalized = strtolower($name);
@ -324,7 +324,92 @@ trait MessageTrait
} }
} }
private function setHeaders(array $headers): static public function setProtocolVersion(string $version): static
{
$this->protocol = $version;
return $this;
}
public function setHeader(string $name, mixed $value): static
{
if (! is_array($value)) {
$value = [$value];
}
$value = $this->trimHeaderValues($value);
$normalized = strtolower($name);
if (isset($this->headerNames[$normalized])) {
unset($this->headers[$this->headerNames[$normalized]]);
}
$this->headerNames[$normalized] = $name;
$this->headers[$name] = $value;
return $this;
}
public function addHeader(string $name, mixed $value): static
{
if (! is_array($value)) {
$value = [$value];
}
$value = $this->trimHeaderValues($value);
$normalized = strtolower($name);
if (isset($this->headerNames[$normalized])) {
$name = $this->headerNames[$normalized];
$this->headers[$name] = array_merge($this->headers[$name], $value);
} else {
$this->headerNames[$normalized] = $name;
$this->headers[$name] = $value;
}
return $this;
}
public function unsetHeader(string $name): static
{
$normalized = strtolower($name);
if (! isset($this->headerNames[$normalized])) {
return $this;
}
$name = $this->headerNames[$normalized];
unset($this->headers[$name], $this->headerNames[$normalized]);
return $this;
}
public function getStandardHeaders(): array
{
$headers = $this->getHeaders();
if (! $this->hasHeader('connection')) {
$headers['Connection'] = [$this->shouldKeepAlive() ? 'keep-alive' : 'close'];
}
if (! $this->hasHeader('content-length')) {
$headers['Content-Length'] = [(string) ($this->getBody()->getSize() ?? 0)];
}
return $headers;
}
public function shouldKeepAlive(): bool
{
return strtolower($this->getHeaderLine('Connection')) === 'keep-alive';
}
public function setBody(StreamInterface $body): static
{
$this->stream = $body;
return $this;
}
/**
* @param array<string, array<string>|string> $headers
*/
public function setHeaders(array $headers): static
{ {
$this->headerNames = $this->headers = []; $this->headerNames = $this->headers = [];
foreach ($headers as $header => $value) { foreach ($headers as $header => $value) {

View File

@ -17,8 +17,9 @@ use InvalidArgumentException;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface; use Psr\Http\Message\UriInterface;
use Swow\Psr7\Message\RequestPlusInterface;
class Request implements RequestInterface class Request implements RequestInterface, RequestPlusInterface
{ {
use MessageTrait; use MessageTrait;
@ -62,6 +63,11 @@ class Request implements RequestInterface
} }
} }
public function __toString(): string
{
return $this->toString();
}
/** /**
* Retrieves the message's request target. * Retrieves the message's request target.
* Retrieves the message's request-target either as it will appear (for * Retrieves the message's request-target either as it will appear (for
@ -102,9 +108,9 @@ class Request implements RequestInterface
* *
* @see http://tools.ietf.org/html/rfc7230#section-5.3 (for the various * @see http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
* request-target forms allowed in request messages) * request-target forms allowed in request messages)
* @param mixed $requestTarget * @param string $requestTarget
*/ */
public function withRequestTarget($requestTarget): static public function withRequestTarget(mixed $requestTarget): static
{ {
if (preg_match('#\s#', $requestTarget)) { if (preg_match('#\s#', $requestTarget)) {
throw new InvalidArgumentException('Invalid request target provided; cannot contain whitespace'); throw new InvalidArgumentException('Invalid request target provided; cannot contain whitespace');
@ -137,7 +143,7 @@ class Request implements RequestInterface
* @param string $method case-sensitive method * @param string $method case-sensitive method
* @throws InvalidArgumentException for invalid HTTP methods * @throws InvalidArgumentException for invalid HTTP methods
*/ */
public function withMethod($method): static public function withMethod(mixed $method): static
{ {
$method = strtoupper($method); $method = strtoupper($method);
$methods = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'HEAD']; $methods = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'HEAD'];
@ -203,6 +209,58 @@ class Request implements RequestInterface
return $new; return $new;
} }
public function setMethod(string $method): static
{
$method = strtoupper($method);
$methods = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'HEAD'];
if (! in_array($method, $methods)) {
throw new InvalidArgumentException('Invalid Method');
}
$this->method = $method;
return $this;
}
public function setUri(UriInterface|string $uri, ?bool $preserveHost = null): static
{
$this->uri = $uri;
if (! $preserveHost) {
$this->updateHostFromUri();
}
return $this;
}
public function setRequestTarget(string $requestTarget): static
{
if (preg_match('#\s#', $requestTarget)) {
throw new InvalidArgumentException('Invalid request target provided; cannot contain whitespace');
}
$this->requestTarget = $requestTarget;
return $this;
}
public function toString(bool $withoutBody = false): string
{
$headerString = '';
if (! $withoutBody) {
foreach ($this->getStandardHeaders() as $key => $values) {
foreach ($values as $value) {
$headerString .= sprintf("%s: %s\r\n", $key, $value);
}
}
}
return sprintf(
"%s %s HTTP/%s\r\n%s\r\n%s",
$this->getMethod(),
$this->getUri()->getPath(),
$this->getProtocolVersion(),
$headerString,
$this->getBody()
);
}
/** /**
* Update Host Header according to Uri. * Update Host Header according to Uri.
* *

View File

@ -13,8 +13,9 @@ namespace Hyperf\HttpMessage\Base;
use InvalidArgumentException; use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Swow\Psr7\Message\ResponsePlusInterface;
class Response implements ResponseInterface class Response implements ResponseInterface, ResponsePlusInterface
{ {
use MessageTrait; use MessageTrait;
@ -122,10 +123,9 @@ class Response implements ResponseInterface
* *
* @param string $name the attribute name * @param string $name the attribute name
* @param mixed $default default value to return if the attribute does not exist * @param mixed $default default value to return if the attribute does not exist
* @return mixed
* @see getAttributes() * @see getAttributes()
*/ */
public function getAttribute($name, $default = null) public function getAttribute(string $name, mixed $default = null): mixed
{ {
return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
} }
@ -142,7 +142,7 @@ class Response implements ResponseInterface
* @param mixed $value the value of the attribute * @param mixed $value the value of the attribute
* @see getAttributes() * @see getAttributes()
*/ */
public function withAttribute($name, $value): static public function withAttribute(string $name, mixed $value): static
{ {
$clone = clone $this; $clone = clone $this;
$clone->attributes[$name] = $value; $clone->attributes[$name] = $value;
@ -331,4 +331,32 @@ class Response implements ResponseInterface
{ {
return in_array($this->statusCode, [204, 304]); return in_array($this->statusCode, [204, 304]);
} }
public function toString(bool $withoutBody = false): string
{
$headerString = '';
foreach ($this->getStandardHeaders() as $key => $values) {
foreach ($values as $value) {
$headerString .= sprintf("%s: %s\r\n", $key, $value);
}
}
return sprintf(
"HTTP/%s %s %s\r\n%s\r\n%s",
$this->getProtocolVersion(),
$this->getStatusCode(),
$this->getReasonPhrase(),
$headerString,
$this->getBody()
);
}
public function setStatus(int $code, string $reasonPhrase = ''): static
{
$this->statusCode = $code;
if (! $reasonPhrase && isset(self::$phrases[$code])) {
$reasonPhrase = self::$phrases[$code];
}
$this->reasonPhrase = $reasonPhrase;
return $this;
}
} }

View File

@ -23,8 +23,9 @@ use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UploadedFileInterface; use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\UriInterface; use Psr\Http\Message\UriInterface;
use Swoole; use Swoole;
use Swow\Psr7\Message\ServerRequestPlusInterface;
class Request extends \Hyperf\HttpMessage\Base\Request implements ServerRequestInterface class Request extends \Hyperf\HttpMessage\Base\Request implements ServerRequestInterface, ServerRequestPlusInterface
{ {
protected ?Swoole\Http\Request $swooleRequest = null; protected ?Swoole\Http\Request $swooleRequest = null;
@ -221,7 +222,7 @@ class Request extends \Hyperf\HttpMessage\Base\Request implements ServerRequestI
* @return null|array|object The deserialized body parameters, if any. * @return null|array|object The deserialized body parameters, if any.
* These will typically be an array or object. * These will typically be an array or object.
*/ */
public function getParsedBody() public function getParsedBody(): array|object|null
{ {
return $this->parsedBody; return $this->parsedBody;
} }
@ -315,10 +316,9 @@ class Request extends \Hyperf\HttpMessage\Base\Request implements ServerRequestI
* *
* @param string $name the attribute name * @param string $name the attribute name
* @param mixed $default default value to return if the attribute does not exist * @param mixed $default default value to return if the attribute does not exist
* @return mixed
* @see getAttributes() * @see getAttributes()
*/ */
public function getAttribute($name, $default = null) public function getAttribute(mixed $name, mixed $default = null): mixed
{ {
return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
} }
@ -335,7 +335,7 @@ class Request extends \Hyperf\HttpMessage\Base\Request implements ServerRequestI
* @param mixed $value the value of the attribute * @param mixed $value the value of the attribute
* @see getAttributes() * @see getAttributes()
*/ */
public function withAttribute($name, $value): static public function withAttribute(mixed $name, mixed $value): static
{ {
$clone = clone $this; $clone = clone $this;
$clone->attributes[$name] = $value; $clone->attributes[$name] = $value;
@ -417,6 +417,53 @@ class Request extends \Hyperf\HttpMessage\Base\Request implements ServerRequestI
return $this; return $this;
} }
public function setServerParams(array $serverParams): static
{
$this->serverParams = $serverParams;
return $this;
}
public function setQueryParams(array $query): static
{
$this->queryParams = $query;
return $this;
}
public function setCookieParams(array $cookies): static
{
$this->cookieParams = $cookies;
return $this;
}
public function setParsedBody(object|array|null $data): static
{
$this->parsedBody = $data;
return $this;
}
public function setUploadedFiles(array $uploadedFiles): static
{
$this->uploadedFiles = $uploadedFiles;
return $this;
}
public function setAttribute(string $name, mixed $value): static
{
$this->attributes[$name] = $value;
return $this;
}
public function unsetAttribute(string $name): static
{
if (array_key_exists($name, $this->attributes) === false) {
return $this;
}
unset($this->attributes[$name]);
return $this;
}
protected static function normalizeParsedBody(array $data = [], ?RequestInterface $request = null): array protected static function normalizeParsedBody(array $data = [], ?RequestInterface $request = null): array
{ {
if (! $request) { if (! $request) {

View File

@ -59,7 +59,7 @@ class Response extends \Hyperf\HttpMessage\Base\Response implements Chunkable
* Returns an instance with specified trailer. * Returns an instance with specified trailer.
* @param string $value * @param string $value
*/ */
public function withTrailer(string $key, $value): static public function withTrailer(string $key, mixed $value): static
{ {
$new = clone $this; $new = clone $this;
$new->trailers[$key] = $value; $new->trailers[$key] = $value;

View File

@ -14,8 +14,9 @@ namespace Hyperf\HttpMessage\Uri;
use InvalidArgumentException; use InvalidArgumentException;
use Psr\Http\Message\UriInterface; use Psr\Http\Message\UriInterface;
use Stringable; use Stringable;
use Swow\Psr7\Message\UriPlusInterface;
class Uri implements UriInterface, Stringable class Uri implements UriInterface, Stringable, UriPlusInterface
{ {
/** /**
* Absolute http and https URIs require a host per RFC 7230 Section 2.7 * Absolute http and https URIs require a host per RFC 7230 Section 2.7
@ -71,6 +72,9 @@ class Uri implements UriInterface, Stringable
*/ */
private string $fragment = ''; private string $fragment = '';
/** @var null|array<string, string> */
private ?array $queryParams = null;
/** /**
* @param string $uri URI to parse * @param string $uri URI to parse
*/ */
@ -149,13 +153,19 @@ class Uri implements UriInterface, Stringable
*/ */
public function getAuthority(): string public function getAuthority(): string
{ {
if ($this->host === '') {
return '';
}
$authority = $this->host; $authority = $this->host;
if ($this->userInfo !== '') { if ($this->userInfo !== '') {
$authority = $this->userInfo . '@' . $authority; $authority = $this->userInfo . '@' . $authority;
} }
if ($this->port !== null) { if ($this->port !== null) {
$authority .= ':' . $this->port; $authority .= ':' . $this->port;
} }
return $authority; return $authority;
} }
@ -284,7 +294,7 @@ class Uri implements UriInterface, Stringable
* @return static a new instance with the specified scheme * @return static a new instance with the specified scheme
* @throws InvalidArgumentException for invalid or unsupported schemes * @throws InvalidArgumentException for invalid or unsupported schemes
*/ */
public function withScheme($scheme): static public function withScheme(mixed $scheme): static
{ {
$scheme = $this->filterScheme($scheme); $scheme = $this->filterScheme($scheme);
if ($this->scheme === $scheme) { if ($this->scheme === $scheme) {
@ -310,7 +320,7 @@ class Uri implements UriInterface, Stringable
* @param null|string $password the password associated with $user * @param null|string $password the password associated with $user
* @return static a new instance with the specified user information * @return static a new instance with the specified user information
*/ */
public function withUserInfo($user, $password = null): static public function withUserInfo(mixed $user, mixed $password = null): static
{ {
$info = $user; $info = $user;
if ($password !== '') { if ($password !== '') {
@ -320,9 +330,9 @@ class Uri implements UriInterface, Stringable
return $this; return $this;
} }
$clone = clone $this; $clone = clone $this;
$clone->userInfo = $user; $clone->userInfo = $info;
$clone->validateState(); $clone->validateState();
return $this; return $clone;
} }
/** /**
@ -335,7 +345,7 @@ class Uri implements UriInterface, Stringable
* @return static a new instance with the specified host * @return static a new instance with the specified host
* @throws InvalidArgumentException for invalid hostnames * @throws InvalidArgumentException for invalid hostnames
*/ */
public function withHost($host): static public function withHost(mixed $host): static
{ {
$host = $this->filterHost($host); $host = $this->filterHost($host);
if ($this->host === $host) { if ($this->host === $host) {
@ -391,7 +401,7 @@ class Uri implements UriInterface, Stringable
* @return static a new instance with the specified path * @return static a new instance with the specified path
* @throws InvalidArgumentException for invalid paths * @throws InvalidArgumentException for invalid paths
*/ */
public function withPath($path): static public function withPath(mixed $path): static
{ {
$path = $this->filterPath($path); $path = $this->filterPath($path);
if ($this->path === $path) { if ($this->path === $path) {
@ -415,7 +425,7 @@ class Uri implements UriInterface, Stringable
* @return static a new instance with the specified query string * @return static a new instance with the specified query string
* @throws InvalidArgumentException for invalid query strings * @throws InvalidArgumentException for invalid query strings
*/ */
public function withQuery($query): static public function withQuery(mixed $query): static
{ {
$query = $this->filterQueryAndFragment($query); $query = $this->filterQueryAndFragment($query);
if ($this->query === $query) { if ($this->query === $query) {
@ -475,7 +485,7 @@ class Uri implements UriInterface, Stringable
* @param string $fragment the fragment to use with the new instance * @param string $fragment the fragment to use with the new instance
* @return static a new instance with the specified fragment * @return static a new instance with the specified fragment
*/ */
public function withFragment($fragment): static public function withFragment(mixed $fragment): static
{ {
$fragment = $this->filterQueryAndFragment($fragment); $fragment = $this->filterQueryAndFragment($fragment);
if ($this->fragment === $fragment) { if ($this->fragment === $fragment) {
@ -539,6 +549,122 @@ class Uri implements UriInterface, Stringable
return self::$defaultPorts[$this->getScheme()] ?? null; return self::$defaultPorts[$this->getScheme()] ?? null;
} }
public function setScheme(string $scheme): static
{
$scheme = $this->filterScheme($scheme);
if ($this->scheme === $scheme) {
return $this;
}
$this->scheme = $scheme;
// TODO add method
$this->removeDefaultPort();
$this->validateState();
return $this;
}
public function setUserInfo(string $user, string $password = ''): static
{
$info = $user;
if ($password !== '') {
$info .= ':' . $password;
}
$this->userInfo = $info;
$this->validateState();
return $this;
}
public function setHost(string $host): static
{
$this->host = $this->filterHost($host);
$this->validateState();
return $this;
}
public function setPort(?int $port): static
{
$port = $this->filterPort($port);
if ($this->port === $port) {
return $this;
}
$this->port = $port;
$this->validateState();
return $this;
}
public function setPath(string $path): static
{
$path = $this->filterPath($path);
if ($this->path === $path) {
return $this;
}
$this->path = $path;
$this->validateState();
return $this;
}
public function setQuery(string $query): static
{
$query = $this->filterQueryAndFragment($query);
$this->query = $query;
return $this;
}
public function getQueryParams(): array
{
if (! isset($this->queryParams)) {
$query = $this->query;
if ($query === '') {
$this->queryParams = [];
} else {
parse_str($query, $this->queryParams);
}
}
return $this->queryParams;
}
public function setQueryParams(array $queryParams): static
{
$this->query = http_build_query($queryParams);
$this->queryParams = $queryParams;
return $this;
}
public function withQueryParams(array $queryParams): static
{
return (clone $this)->setQueryParams($queryParams);
}
public function setFragment(string $fragment): static
{
$fragment = $this->filterQueryAndFragment($fragment);
$this->fragment = $fragment;
return $this;
}
public static function build(string $scheme, string $authority, string $path, string $query, string $fragment): string
{
$schemeSuffix = $scheme !== '' ? ':' : '';
$authorityPrefix = $authority !== '' ? '//' : '';
$pathPrefix = '';
if ($path !== '' && ! str_starts_with($path, '/') && $authority !== '') {
// If the path is rootless and an authority is present, the path MUST be prefixed by "/"
$pathPrefix = '/';
}
$queryPrefix = $query !== '' ? '?' : '';
$fragmentPrefix = $fragment !== '' ? '#' : '';
return $scheme . $schemeSuffix . $authorityPrefix . $authority . $pathPrefix . $path . $queryPrefix . $query . $fragmentPrefix . $fragment;
}
public function toString(): string
{
return static::build($this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment);
}
/** /**
* Common state validate method. * Common state validate method.
*/ */