Request.php 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This file is part of Hyperf.
  5. *
  6. * @link https://www.hyperf.io
  7. * @document https://hyperf.wiki
  8. * @contact group@hyperf.io
  9. * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
  10. */
  11. namespace Hyperf\HttpMessage\Base;
  12. use Hyperf\Engine\Http\Http;
  13. use Hyperf\HttpMessage\Stream\SwooleStream;
  14. use Hyperf\HttpMessage\Uri\Uri;
  15. use InvalidArgumentException;
  16. use Psr\Http\Message\RequestInterface;
  17. use Psr\Http\Message\StreamInterface;
  18. use Psr\Http\Message\UriInterface;
  19. use Stringable;
  20. use Swow\Psr7\Message\RequestPlusInterface;
  21. class Request implements RequestInterface, RequestPlusInterface, Stringable
  22. {
  23. use MessageTrait;
  24. protected array $server = [];
  25. protected UriInterface $uri;
  26. protected string $method;
  27. protected ?string $requestTarget = null;
  28. /**
  29. * @param string $method HTTP method
  30. * @param string|UriInterface $uri URI
  31. * @param array $headers Request headers
  32. * @param null|resource|StreamInterface|string $body Request body
  33. * @param string $version Protocol version
  34. */
  35. public function __construct(
  36. string $method,
  37. string|UriInterface $uri,
  38. array $headers = [],
  39. mixed $body = null,
  40. string $version = '1.1'
  41. ) {
  42. if (! $uri instanceof UriInterface) {
  43. $uri = new Uri($uri);
  44. }
  45. $this->method = strtoupper($method);
  46. $this->uri = $uri;
  47. $this->setHeaders($headers);
  48. $this->protocol = $version;
  49. if (! $this->hasHeader('Host')) {
  50. $this->updateHostFromUri();
  51. }
  52. if ($body !== '' && $body !== null) {
  53. $this->stream = ($body instanceof StreamInterface ? $body : new SwooleStream($body));
  54. }
  55. }
  56. public function __toString(): string
  57. {
  58. return $this->toString();
  59. }
  60. /**
  61. * Retrieves the message's request target.
  62. * Retrieves the message's request-target either as it will appear (for
  63. * clients), as it appeared at request (for servers), or as it was
  64. * specified for the instance (see withRequestTarget()).
  65. * In most cases, this will be the origin-form of the composed URI,
  66. * unless a value was provided to the concrete implementation (see
  67. * withRequestTarget() below).
  68. * If no URI is available, and no request-target has been specifically
  69. * provided, this method MUST return the string "/".
  70. */
  71. public function getRequestTarget(): string
  72. {
  73. if ($this->requestTarget !== null) {
  74. return $this->requestTarget;
  75. }
  76. $target = $this->uri->getPath();
  77. if ($target == '') {
  78. $target = '/';
  79. }
  80. if ($this->uri->getQuery() != '') {
  81. $target .= '?' . $this->uri->getQuery();
  82. }
  83. return $target;
  84. }
  85. /**
  86. * Return an instance with the specific request-target.
  87. * If the request needs a non-origin-form request-target — e.g., for
  88. * specifying an absolute-form, authority-form, or asterisk-form —
  89. * this method may be used to create an instance with the specified
  90. * request-target, verbatim.
  91. * This method MUST be implemented in such a way as to retain the
  92. * immutability of the message, and MUST return an instance that has the
  93. * changed request target.
  94. *
  95. * @see http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
  96. * request-target forms allowed in request messages)
  97. * @param string $requestTarget
  98. */
  99. public function withRequestTarget(mixed $requestTarget): static
  100. {
  101. if (preg_match('#\s#', $requestTarget)) {
  102. throw new InvalidArgumentException('Invalid request target provided; cannot contain whitespace');
  103. }
  104. $new = clone $this;
  105. $new->requestTarget = $requestTarget;
  106. return $new;
  107. }
  108. /**
  109. * Retrieves the HTTP method of the request.
  110. *
  111. * @return string returns the request method
  112. */
  113. public function getMethod(): string
  114. {
  115. return $this->method;
  116. }
  117. /**
  118. * Return an instance with the provided HTTP method.
  119. * While HTTP method names are typically all uppercase characters, HTTP
  120. * method names are case-sensitive and thus implementations SHOULD NOT
  121. * modify the given string.
  122. * This method MUST be implemented in such a way as to retain the
  123. * immutability of the message, and MUST return an instance that has the
  124. * changed request method.
  125. *
  126. * @param string $method case-sensitive method
  127. * @throws InvalidArgumentException for invalid HTTP methods
  128. */
  129. public function withMethod(mixed $method): static
  130. {
  131. $method = strtoupper($method);
  132. $methods = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'HEAD'];
  133. if (! in_array($method, $methods)) {
  134. throw new InvalidArgumentException('Invalid Method');
  135. }
  136. $new = clone $this;
  137. $new->method = $method;
  138. return $new;
  139. }
  140. /**
  141. * Retrieves the URI instance.
  142. * This method MUST return a UriInterface instance.
  143. *
  144. * @see http://tools.ietf.org/html/rfc3986#section-4.3
  145. * @return UriInterface returns a UriInterface instance
  146. * representing the URI of the request
  147. */
  148. public function getUri(): UriInterface
  149. {
  150. return $this->uri;
  151. }
  152. /**
  153. * Returns an instance with the provided URI.
  154. * This method MUST update the Host header of the returned request by
  155. * default if the URI contains a host component. If the URI does not
  156. * contain a host component, any pre-existing Host header MUST be carried
  157. * over to the returned request.
  158. * You can opt in to preserving the original state of the Host header by
  159. * setting `$preserveHost` to `true`. When `$preserveHost` is set to
  160. * `true`, this method interacts with the Host header in the following ways:
  161. * - If the Host header is missing or empty, and the new URI contains
  162. * a host component, this method MUST update the Host header in the returned
  163. * request.
  164. * - If the Host header is missing or empty, and the new URI does not contain a
  165. * host component, this method MUST NOT update the Host header in the returned
  166. * request.
  167. * - If a Host header is present and non-empty, this method MUST NOT update
  168. * the Host header in the returned request.
  169. * This method MUST be implemented in such a way as to retain the
  170. * immutability of the message, and MUST return an instance that has the
  171. * new UriInterface instance.
  172. *
  173. * @see http://tools.ietf.org/html/rfc3986#section-4.3
  174. * @param UriInterface $uri new request URI to use
  175. * @param bool $preserveHost preserve the original state of the Host header
  176. */
  177. public function withUri(UriInterface $uri, $preserveHost = false): static
  178. {
  179. if ($uri === $this->uri) {
  180. return $this;
  181. }
  182. $new = clone $this;
  183. $new->uri = $uri;
  184. if (! $preserveHost) {
  185. $new->updateHostFromUri();
  186. }
  187. return $new;
  188. }
  189. public function setMethod(string $method): static
  190. {
  191. $method = strtoupper($method);
  192. $methods = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'HEAD'];
  193. if (! in_array($method, $methods)) {
  194. throw new InvalidArgumentException('Invalid Method');
  195. }
  196. $this->method = $method;
  197. return $this;
  198. }
  199. public function setUri(string|UriInterface $uri, ?bool $preserveHost = null): static
  200. {
  201. $this->uri = $uri;
  202. if (! $preserveHost) {
  203. $this->updateHostFromUri();
  204. }
  205. return $this;
  206. }
  207. public function setRequestTarget(string $requestTarget): static
  208. {
  209. if (preg_match('#\s#', $requestTarget)) {
  210. throw new InvalidArgumentException('Invalid request target provided; cannot contain whitespace');
  211. }
  212. $this->requestTarget = $requestTarget;
  213. return $this;
  214. }
  215. public function toString(bool $withoutBody = false): string
  216. {
  217. return Http::packRequest(
  218. $this->getMethod(),
  219. $this->getUri()->getPath(),
  220. $this->getStandardHeaders(),
  221. $withoutBody ? '' : (string) $this->getBody(),
  222. $this->getProtocolVersion()
  223. );
  224. }
  225. /**
  226. * Update Host Header according to Uri.
  227. *
  228. * @see http://tools.ietf.org/html/rfc7230#section-5.4
  229. */
  230. private function updateHostFromUri(): void
  231. {
  232. $host = $this->uri->getHost();
  233. if ($host === '') {
  234. return;
  235. }
  236. if (($port = $this->uri->getPort()) !== null) {
  237. $host .= ':' . $port;
  238. }
  239. $header = 'host';
  240. if ($this->hasHeader('host')) {
  241. $host = $this->getHeaderLine('host');
  242. } else {
  243. $this->headerNames['host'] = 'host';
  244. }
  245. // Ensure Host is the first header.
  246. $this->headers = [$header => [$host]] + $this->headers;
  247. }
  248. }