Request.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  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\HttpServer;
  12. use Hyperf\Collection\Arr;
  13. use Hyperf\Context\Context;
  14. use Hyperf\Context\RequestContext;
  15. use Hyperf\HttpMessage\Upload\UploadedFile;
  16. use Hyperf\HttpServer\Contract\RequestInterface;
  17. use Hyperf\HttpServer\Router\Dispatched;
  18. use Hyperf\Macroable\Macroable;
  19. use Hyperf\Stringable\Str;
  20. use Psr\Http\Message\MessageInterface;
  21. use Psr\Http\Message\RequestInterface as Psr7RequestInterface;
  22. use Psr\Http\Message\ServerRequestInterface;
  23. use Psr\Http\Message\StreamInterface;
  24. use Psr\Http\Message\UriInterface;
  25. use RuntimeException;
  26. use function Hyperf\Collection\data_get;
  27. use function Hyperf\Support\value;
  28. /**
  29. * @property string $pathInfo
  30. * @property string $requestUri
  31. */
  32. class Request implements RequestInterface
  33. {
  34. use Macroable;
  35. /**
  36. * @var array the keys to identify the data of request in coroutine context
  37. */
  38. protected array $contextkeys
  39. = [
  40. 'parsedData' => 'http.request.parsedData',
  41. ];
  42. public function __get($name)
  43. {
  44. return $this->getRequestProperty($name);
  45. }
  46. public function __set($name, $value)
  47. {
  48. $this->storeRequestProperty($name, $value);
  49. }
  50. /**
  51. * Retrieve the data from query parameters, if $key is null, will return all query parameters.
  52. */
  53. public function query(?string $key = null, mixed $default = null): mixed
  54. {
  55. if ($key === null) {
  56. return $this->getQueryParams();
  57. }
  58. return data_get($this->getQueryParams(), $key, $default);
  59. }
  60. /**
  61. * Retrieve the data from route parameters.
  62. */
  63. public function route(string $key, mixed $default = null): mixed
  64. {
  65. /** @var null|Dispatched $route */
  66. $route = $this->getAttribute(Dispatched::class);
  67. if (is_null($route)) {
  68. return $default;
  69. }
  70. return array_key_exists($key, $route->params) ? $route->params[$key] : $default;
  71. }
  72. /**
  73. * Retrieve the data from parsed body, if $key is null, will return all parsed body.
  74. */
  75. public function post(?string $key = null, mixed $default = null): mixed
  76. {
  77. if ($key === null) {
  78. return $this->getParsedBody();
  79. }
  80. return data_get($this->getParsedBody(), $key, $default);
  81. }
  82. /**
  83. * Retrieve the input data from request, include query parameters, parsed body and json body.
  84. */
  85. public function input(string $key, mixed $default = null): mixed
  86. {
  87. $data = $this->getInputData();
  88. return data_get($data, $key, $default);
  89. }
  90. /**
  91. * Retrieve the input data from request via multi keys, include query parameters, parsed body and json body.
  92. */
  93. public function inputs(array $keys, ?array $default = null): array
  94. {
  95. $data = $this->getInputData();
  96. $result = [];
  97. foreach ($keys as $key) {
  98. $result[$key] = data_get($data, $key, $default[$key] ?? null);
  99. }
  100. return $result;
  101. }
  102. /**
  103. * Retrieve all input data from request, include query parameters, parsed body and json body.
  104. */
  105. public function all(): array
  106. {
  107. return $this->getInputData();
  108. }
  109. /**
  110. * Determine if the $keys is existed in parameters.
  111. *
  112. * @return array [found, not-found]
  113. */
  114. public function hasInput(array $keys): array
  115. {
  116. $data = $this->getInputData();
  117. $found = [];
  118. foreach ($keys as $key) {
  119. if (Arr::has($data, $key)) {
  120. $found[] = $key;
  121. }
  122. }
  123. return [
  124. $found,
  125. array_diff($keys, $found),
  126. ];
  127. }
  128. /**
  129. * Determine if the $keys is existed in parameters.
  130. */
  131. public function has(array|string $keys): bool
  132. {
  133. return Arr::has($this->getInputData(), $keys);
  134. }
  135. /**
  136. * Retrieve the data from request headers.
  137. */
  138. public function header(string $key, ?string $default = null): ?string
  139. {
  140. if (! $this->hasHeader($key)) {
  141. return $default;
  142. }
  143. return $this->getHeaderLine($key);
  144. }
  145. /**
  146. * Get the current path info for the request.
  147. */
  148. public function path(): string
  149. {
  150. $pattern = trim($this->getPathInfo(), '/');
  151. return $pattern == '' ? '/' : $pattern;
  152. }
  153. /**
  154. * Returns the path being requested relative to the executed script.
  155. * The path info always starts with a /.
  156. * Suppose this request is instantiated from /mysite on localhost:
  157. * * http://localhost/mysite returns an empty string
  158. * * http://localhost/mysite/about returns '/about'
  159. * * http://localhost/mysite/enco%20ded returns '/enco%20ded'
  160. * * http://localhost/mysite/about?var=1 returns '/about'.
  161. *
  162. * @return string The raw path (i.e. not urldecoded)
  163. */
  164. public function getPathInfo(): string
  165. {
  166. if ($this->pathInfo === null) {
  167. $this->pathInfo = $this->preparePathInfo();
  168. }
  169. return $this->pathInfo ?? '';
  170. }
  171. /**
  172. * Determine if the current request URI matches a pattern.
  173. *
  174. * @param mixed ...$patterns
  175. */
  176. public function is(...$patterns): bool
  177. {
  178. foreach ($patterns as $pattern) {
  179. if (Str::is($pattern, $this->decodedPath())) {
  180. return true;
  181. }
  182. }
  183. return false;
  184. }
  185. /**
  186. * Get the current decoded path info for the request.
  187. */
  188. public function decodedPath(): string
  189. {
  190. return rawurldecode($this->path());
  191. }
  192. /**
  193. * Returns the requested URI (path and query string).
  194. *
  195. * @return string The raw URI (i.e. not URI decoded)
  196. */
  197. public function getRequestUri(): string
  198. {
  199. if ($this->requestUri === null) {
  200. $this->requestUri = $this->prepareRequestUri();
  201. }
  202. return $this->requestUri;
  203. }
  204. /**
  205. * Get the URL (no query string) for the request.
  206. */
  207. public function url(): string
  208. {
  209. return rtrim(preg_replace('/\?.*/', '', (string) $this->getUri()), '/');
  210. }
  211. /**
  212. * Get the full URL for the request.
  213. */
  214. public function fullUrl(): string
  215. {
  216. $query = $this->getQueryString();
  217. return $this->url() . '?' . $query;
  218. }
  219. /**
  220. * Generates the normalized query string for the Request.
  221. *
  222. * It builds a normalized query string, where keys/value pairs are alphabetized
  223. * and have consistent escaping.
  224. *
  225. * @return null|string A normalized query string for the Request
  226. */
  227. public function getQueryString(): ?string
  228. {
  229. $qs = static::normalizeQueryString($this->getServerParams()['query_string'] ?? '');
  230. return $qs === '' ? null : $qs;
  231. }
  232. /**
  233. * Normalizes a query string.
  234. *
  235. * It builds a normalized query string, where keys/value pairs are alphabetized,
  236. * have consistent escaping and unneeded delimiters are removed.
  237. *
  238. * @param string $qs Query string
  239. * @return string A normalized query string for the Request
  240. */
  241. public function normalizeQueryString(string $qs): string
  242. {
  243. if ($qs == '') {
  244. return '';
  245. }
  246. parse_str($qs, $qs);
  247. ksort($qs);
  248. return http_build_query($qs, '', '&', PHP_QUERY_RFC3986);
  249. }
  250. /**
  251. * Retrieve a cookie from the request.
  252. */
  253. public function cookie(string $key, mixed $default = null)
  254. {
  255. return data_get($this->getCookieParams(), $key, $default);
  256. }
  257. /**
  258. * Determine if a cookie is set on the request.
  259. */
  260. public function hasCookie(string $key): bool
  261. {
  262. return ! is_null($this->cookie($key));
  263. }
  264. /**
  265. * Retrieve a server variable from the request.
  266. */
  267. public function server(string $key, mixed $default = null): mixed
  268. {
  269. return data_get($this->getServerParams(), $key, $default);
  270. }
  271. /**
  272. * Checks if the request method is of specified type.
  273. *
  274. * @param string $method Uppercase request method (GET, POST etc)
  275. */
  276. public function isMethod(string $method): bool
  277. {
  278. return $this->getMethod() === strtoupper($method);
  279. }
  280. /**
  281. * Retrieve a file from the request.
  282. *
  283. * @return null|UploadedFile|UploadedFile[]
  284. */
  285. public function file(string $key, mixed $default = null)
  286. {
  287. return Arr::get($this->getUploadedFiles(), $key, $default);
  288. }
  289. /**
  290. * Determine if the uploaded data contains a file.
  291. */
  292. public function hasFile(string $key): bool
  293. {
  294. if ($this->file($key)) {
  295. return true;
  296. }
  297. return false;
  298. }
  299. public function getProtocolVersion(): string
  300. {
  301. return $this->call(__FUNCTION__, func_get_args());
  302. }
  303. public function withProtocolVersion($version): MessageInterface
  304. {
  305. return $this->call(__FUNCTION__, func_get_args());
  306. }
  307. public function getHeaders(): array
  308. {
  309. return $this->call(__FUNCTION__, func_get_args());
  310. }
  311. public function hasHeader($name): bool
  312. {
  313. return $this->call(__FUNCTION__, func_get_args());
  314. }
  315. public function getHeader($name): array
  316. {
  317. return $this->call(__FUNCTION__, func_get_args());
  318. }
  319. public function getHeaderLine($name): string
  320. {
  321. return $this->call(__FUNCTION__, func_get_args());
  322. }
  323. public function withHeader($name, $value): MessageInterface
  324. {
  325. return $this->call(__FUNCTION__, func_get_args());
  326. }
  327. public function withAddedHeader($name, $value): MessageInterface
  328. {
  329. return $this->call(__FUNCTION__, func_get_args());
  330. }
  331. public function withoutHeader($name): MessageInterface
  332. {
  333. return $this->call(__FUNCTION__, func_get_args());
  334. }
  335. public function getBody(): StreamInterface
  336. {
  337. return $this->call(__FUNCTION__, func_get_args());
  338. }
  339. public function withBody(StreamInterface $body): MessageInterface
  340. {
  341. return $this->call(__FUNCTION__, func_get_args());
  342. }
  343. public function getRequestTarget(): string
  344. {
  345. return $this->call(__FUNCTION__, func_get_args());
  346. }
  347. public function withRequestTarget($requestTarget): Psr7RequestInterface
  348. {
  349. return $this->call(__FUNCTION__, func_get_args());
  350. }
  351. public function getMethod(): string
  352. {
  353. return $this->call(__FUNCTION__, func_get_args());
  354. }
  355. public function withMethod($method): Psr7RequestInterface
  356. {
  357. return $this->call(__FUNCTION__, func_get_args());
  358. }
  359. public function getUri(): UriInterface
  360. {
  361. return $this->call(__FUNCTION__, func_get_args());
  362. }
  363. public function withUri(UriInterface $uri, $preserveHost = false): Psr7RequestInterface
  364. {
  365. return $this->call(__FUNCTION__, func_get_args());
  366. }
  367. public function getServerParams(): array
  368. {
  369. return $this->call(__FUNCTION__, func_get_args());
  370. }
  371. public function getCookieParams(): array
  372. {
  373. return $this->call(__FUNCTION__, func_get_args());
  374. }
  375. public function withCookieParams(array $cookies): ServerRequestInterface
  376. {
  377. return $this->call(__FUNCTION__, func_get_args());
  378. }
  379. public function getQueryParams(): array
  380. {
  381. return $this->call(__FUNCTION__, func_get_args());
  382. }
  383. public function withQueryParams(array $query): ServerRequestInterface
  384. {
  385. return $this->call(__FUNCTION__, func_get_args());
  386. }
  387. public function getUploadedFiles(): array
  388. {
  389. return $this->call(__FUNCTION__, func_get_args());
  390. }
  391. public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface
  392. {
  393. return $this->call(__FUNCTION__, func_get_args());
  394. }
  395. public function getParsedBody()
  396. {
  397. return $this->call(__FUNCTION__, func_get_args());
  398. }
  399. public function withParsedBody($data): ServerRequestInterface
  400. {
  401. return $this->call(__FUNCTION__, func_get_args());
  402. }
  403. public function getAttributes(): array
  404. {
  405. return $this->call(__FUNCTION__, func_get_args());
  406. }
  407. public function getAttribute($name, $default = null)
  408. {
  409. return $this->call(__FUNCTION__, func_get_args());
  410. }
  411. public function withAttribute($name, $value): ServerRequestInterface
  412. {
  413. return $this->call(__FUNCTION__, func_get_args());
  414. }
  415. public function withoutAttribute($name): ServerRequestInterface
  416. {
  417. return $this->call(__FUNCTION__, func_get_args());
  418. }
  419. public function clearStoredParsedData(): void
  420. {
  421. if (Context::has($this->contextkeys['parsedData'])) {
  422. Context::set($this->contextkeys['parsedData'], null);
  423. }
  424. }
  425. /**
  426. * Prepares the path info.
  427. */
  428. protected function preparePathInfo(): string
  429. {
  430. $requestUri = $this->getRequestUri();
  431. // Remove the query string from REQUEST_URI
  432. if (false !== $pos = strpos($requestUri, '?')) {
  433. $requestUri = substr($requestUri, 0, $pos);
  434. }
  435. if ($requestUri !== '' && $requestUri[0] !== '/') {
  436. $requestUri = '/' . $requestUri;
  437. }
  438. return $requestUri;
  439. }
  440. /*
  441. * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24)
  442. *
  443. * Code subject to the new BSD license (http://framework.zend.com/license/new-bsd).
  444. *
  445. * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  446. */
  447. protected function prepareRequestUri(): string
  448. {
  449. $requestUri = '';
  450. $serverParams = $this->getServerParams();
  451. if (isset($serverParams['request_uri'])) {
  452. $requestUri = $serverParams['request_uri'];
  453. if ($requestUri !== '' && $requestUri[0] === '/') {
  454. // To only use path and query remove the fragment.
  455. if (false !== $pos = strpos($requestUri, '#')) {
  456. $requestUri = substr($requestUri, 0, $pos);
  457. }
  458. } else {
  459. // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path,
  460. // only use URL path.
  461. $uriComponents = parse_url($requestUri);
  462. if (isset($uriComponents['path'])) {
  463. $requestUri = $uriComponents['path'];
  464. }
  465. if (isset($uriComponents['query'])) {
  466. $requestUri .= '?' . $uriComponents['query'];
  467. }
  468. }
  469. }
  470. // normalize the request URI to ease creating sub-requests from this request
  471. $serverParams['request_uri'] = $requestUri;
  472. return $requestUri;
  473. }
  474. protected function getInputData(): array
  475. {
  476. return $this->storeParsedData(function () {
  477. $request = $this->getRequest();
  478. if (is_array($request->getParsedBody())) {
  479. $data = $request->getParsedBody();
  480. } else {
  481. $data = [];
  482. }
  483. return $request->getQueryParams() + $data;
  484. });
  485. }
  486. protected function storeParsedData(callable $callback): mixed
  487. {
  488. if (! Context::has($this->contextkeys['parsedData'])) {
  489. return Context::set($this->contextkeys['parsedData'], $callback());
  490. }
  491. return Context::get($this->contextkeys['parsedData']);
  492. }
  493. protected function storeRequestProperty(string $key, mixed $value): static
  494. {
  495. Context::set(__CLASS__ . '.properties.' . $key, value($value));
  496. return $this;
  497. }
  498. protected function getRequestProperty(string $key): mixed
  499. {
  500. return Context::get(__CLASS__ . '.properties.' . $key);
  501. }
  502. protected function call($name, $arguments)
  503. {
  504. $request = $this->getRequest();
  505. if (! method_exists($request, $name)) {
  506. throw new RuntimeException('Method not exist.');
  507. }
  508. return $request->{$name}(...$arguments);
  509. }
  510. protected function getRequest(): ServerRequestInterface
  511. {
  512. return RequestContext::get();
  513. }
  514. }