Request.php 16 KB

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