Request.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  1. <?php
  2. namespace yzh52521\EasyHttp;
  3. use GuzzleHttp\Handler\CurlHandler;
  4. use GuzzleHttp\HandlerStack;
  5. use GuzzleHttp\Middleware;
  6. use GuzzleHttp\Pool;
  7. use GuzzleHttp\Client;
  8. use GuzzleHttp\Promise;
  9. use GuzzleHttp\Cookie\CookieJar;
  10. use GuzzleHttp\Exception\ConnectException;
  11. /**
  12. * @method \yzh52521\EasyHttp\Response body()
  13. * @method \yzh52521\EasyHttp\Response array()
  14. * @method \yzh52521\EasyHttp\Response json()
  15. * @method \yzh52521\EasyHttp\Response headers()
  16. * @method \yzh52521\EasyHttp\Response header(string $header)
  17. * @method \yzh52521\EasyHttp\Response status()
  18. * @method \yzh52521\EasyHttp\Response successful()
  19. * @method \yzh52521\EasyHttp\Response ok()
  20. * @method \yzh52521\EasyHttp\Response redirect()
  21. * @method \yzh52521\EasyHttp\Response clientError()
  22. * @method \yzh52521\EasyHttp\Response serverError()
  23. */
  24. class Request
  25. {
  26. /**
  27. * \GuzzleHttp\Client单例
  28. * @var array
  29. */
  30. private static $instances = [];
  31. /**
  32. * \GuzzleHttp\Client;
  33. * @var Client
  34. */
  35. protected $client;
  36. /**
  37. * Body格式
  38. * @var string
  39. */
  40. protected $bodyFormat;
  41. /**
  42. * The raw body for the request.
  43. *
  44. * @var string
  45. */
  46. protected $pendingBody;
  47. protected $isRemoveBodyFormat = false;
  48. /**
  49. * @var array
  50. */
  51. protected $options = [];
  52. /**
  53. * @var array
  54. */
  55. protected $promises = [];
  56. /**
  57. * 并发次数
  58. * @var
  59. */
  60. protected $concurrency;
  61. /**
  62. *
  63. * @var HandlerStack
  64. */
  65. protected $handlerStack;
  66. /**
  67. * Request constructor.
  68. */
  69. public function __construct()
  70. {
  71. $this->client = $this->getInstance();
  72. $this->bodyFormat = 'form_params';
  73. $this->options = [
  74. 'http_errors' => false,
  75. ];
  76. $this->handlerStack = HandlerStack::create(new CurlHandler());
  77. }
  78. /**
  79. * Request destructor.
  80. */
  81. public function __destruct()
  82. {
  83. }
  84. /**
  85. * 获取单例
  86. * @return mixed
  87. */
  88. public function getInstance()
  89. {
  90. $name = get_called_class();
  91. if (!isset(self::$instances[$name])) {
  92. self::$instances[$name] = new Client();
  93. }
  94. return self::$instances[$name];
  95. }
  96. public function removeOptions()
  97. {
  98. $this->bodyFormat = 'form_params';
  99. $this->isRemoveBodyFormat = false;
  100. $this->options = [
  101. 'http_errors' => false,
  102. 'verify' => false
  103. ];
  104. return $this;
  105. }
  106. public function asForm()
  107. {
  108. $this->bodyFormat = 'form_params';
  109. $this->withHeaders(['Content-Type' => 'application/x-www-form-urlencoded']);
  110. return $this;
  111. }
  112. public function asJson()
  113. {
  114. $this->bodyFormat = 'json';
  115. $this->withHeaders(['Content-Type' => 'application/json']);
  116. return $this;
  117. }
  118. public function asMultipart(string $name, string $contents, string $filename = null, array $headers = [])
  119. {
  120. $this->bodyFormat = 'multipart';
  121. $this->options = array_filter([
  122. 'name' => $name,
  123. 'contents' => $contents,
  124. 'headers' => $headers,
  125. 'filename' => $filename,
  126. ]);
  127. return $this;
  128. }
  129. public function withMiddleware(callable $middleware)
  130. {
  131. $this->handlerStack->push($middleware);
  132. $this->options['handler'] = $this->handlerStack;
  133. return $this;
  134. }
  135. public function withRequestMiddleware(callable $middleware)
  136. {
  137. $this->handlerStack->push(Middleware::mapRequest($middleware));
  138. $this->options['handler'] = $this->handlerStack;
  139. return $this;
  140. }
  141. public function withResponseMiddleware(callable $middleware)
  142. {
  143. $this->handlerStack->push(Middleware::mapResponse($middleware));
  144. $this->options['handler'] = $this->handlerStack;
  145. return $this;
  146. }
  147. public function withHost(string $host)
  148. {
  149. $this->options['base_uri'] = $host;
  150. return $this;
  151. }
  152. public function withOptions(array $options)
  153. {
  154. unset($this->options[$this->bodyFormat], $this->options['body']);
  155. $this->options = array_merge_recursive($this->options, $options);
  156. return $this;
  157. }
  158. public function withCert(string $path, string $password)
  159. {
  160. $this->options['cert'] = [$path, $password];
  161. return $this;
  162. }
  163. public function withHeaders(array $headers)
  164. {
  165. $this->options = array_merge_recursive($this->options, [
  166. 'headers' => $headers,
  167. ]);
  168. return $this;
  169. }
  170. public function withBody($content, $contentType = 'application/json')
  171. {
  172. $this->bodyFormat = 'body';
  173. $this->options['headers']['Content-Type'] = $contentType;
  174. $this->pendingBody = $content;
  175. return $this;
  176. }
  177. public function withBasicAuth(string $username, string $password)
  178. {
  179. $this->options['auth'] = [$username, $password];
  180. return $this;
  181. }
  182. public function withDigestAuth(string $username, string $password)
  183. {
  184. $this->options['auth'] = [$username, $password, 'digest'];
  185. return $this;
  186. }
  187. public function withUA(string $ua)
  188. {
  189. $this->options['headers']['User-Agent'] = trim($ua);
  190. return $this;
  191. }
  192. public function withToken(string $token, string $type = 'Bearer')
  193. {
  194. $this->options['headers']['Authorization'] = trim($type . ' ' . $token);
  195. return $this;
  196. }
  197. public function withCookies(array $cookies, string $domain)
  198. {
  199. $this->options = array_merge_recursive($this->options, [
  200. 'cookies' => CookieJar::fromArray($cookies, $domain),
  201. ]);
  202. return $this;
  203. }
  204. public function withProxy($proxy)
  205. {
  206. $this->options['proxy'] = $proxy;
  207. return $this;
  208. }
  209. public function withVersion($version)
  210. {
  211. $this->options['version'] = $version;
  212. return $this;
  213. }
  214. public function maxRedirects(int $max)
  215. {
  216. $this->options['allow_redirects']['max'] = $max;
  217. return $this;
  218. }
  219. public function withRedirect($redirect = false)
  220. {
  221. $this->options['allow_redirects'] = $redirect;
  222. return $this;
  223. }
  224. public function withVerify($verify = false)
  225. {
  226. $this->options['verify'] = $verify;
  227. return $this;
  228. }
  229. public function withStream($boolean = false)
  230. {
  231. $this->options['stream'] = $boolean;
  232. return $this;
  233. }
  234. public function concurrency(int $times)
  235. {
  236. $this->concurrency = $times;
  237. return $this;
  238. }
  239. public function retry(int $retries = 1, int $sleep = 0)
  240. {
  241. $this->handlerStack->push((new Retry())->handle($retries, $sleep));
  242. $this->options['handler'] = $this->handlerStack;
  243. return $this;
  244. }
  245. public function delay(int $seconds)
  246. {
  247. $this->options['delay'] = $seconds * 1000;
  248. return $this;
  249. }
  250. public function timeout(float $seconds)
  251. {
  252. $this->options['timeout'] = $seconds;
  253. return $this;
  254. }
  255. public function connectTimeout(float $seconds)
  256. {
  257. $this->options['connect_timeout'] = $seconds;
  258. return $this;
  259. }
  260. /**
  261. * @param string|resource $to
  262. * @return $this
  263. */
  264. public function sink($to)
  265. {
  266. $this->options['sink'] = $to;
  267. return $this;
  268. }
  269. public function removeBodyFormat()
  270. {
  271. $this->isRemoveBodyFormat = true;
  272. return $this;
  273. }
  274. public function debug($class)
  275. {
  276. $logger = new Logger(function ($level, $message, array $context) use ($class) {
  277. $class::log($level, $message);
  278. }, function ($request, $response, $reason) {
  279. $requestBody = $request->getBody();
  280. $requestBody->rewind();
  281. //请求头
  282. $requestHeaders = [];
  283. foreach ((array)$request->getHeaders() as $k => $vs) {
  284. foreach ($vs as $v) {
  285. $requestHeaders[] = "$k: $v";
  286. }
  287. }
  288. //响应头
  289. $responseHeaders = [];
  290. foreach ((array)$response->getHeaders() as $k => $vs) {
  291. foreach ($vs as $v) {
  292. $responseHeaders[] = "$k: $v";
  293. }
  294. }
  295. $uri = $request->getUri();
  296. $path = $uri->getPath();
  297. if ($query = $uri->getQuery()) {
  298. $path .= '?' . $query;
  299. }
  300. return sprintf(
  301. "Request %s\n%s %s HTTP/%s\r\n%s\r\n\r\n%s\r\n--------------------\r\nHTTP/%s %s %s\r\n%s\r\n\r\n%s",
  302. $uri,
  303. $request->getMethod(),
  304. $path,
  305. $request->getProtocolVersion(),
  306. join("\r\n", $requestHeaders),
  307. $requestBody->getContents(),
  308. $response->getProtocolVersion(),
  309. $response->getStatusCode(),
  310. $response->getReasonPhrase(),
  311. join("\r\n", $responseHeaders),
  312. $response->getBody()->getContents()
  313. );
  314. });
  315. $this->handlerStack->push($logger);
  316. $this->options['handler'] = $this->handlerStack;
  317. return $this;
  318. }
  319. public function attach(string $name, string $contents, string $filename = null, array $headers = [])
  320. {
  321. $this->options['multipart'] = array_filter([
  322. 'name' => $name,
  323. 'contents' => $contents,
  324. 'headers' => $headers,
  325. 'filename' => $filename,
  326. ]);
  327. return $this;
  328. }
  329. public function get(string $url, array $query = [])
  330. {
  331. $params = parse_url($url, PHP_URL_QUERY);
  332. parse_str($params ?: '', $result);
  333. $this->options['query'] = array_merge($result, $query);
  334. return $this->request('GET', $url, $query);
  335. }
  336. public function post(string $url, array $data = [])
  337. {
  338. $this->options[$this->bodyFormat] = $data;
  339. return $this->request('POST', $url, $data);
  340. }
  341. public function patch(string $url, array $data = [])
  342. {
  343. $this->options[$this->bodyFormat] = $data;
  344. return $this->request('PATCH', $url, $data);
  345. }
  346. public function put(string $url, array $data = [])
  347. {
  348. $this->options[$this->bodyFormat] = $data;
  349. return $this->request('PUT', $url, $data);
  350. }
  351. public function delete(string $url, array $data = [])
  352. {
  353. $this->options[$this->bodyFormat] = $data;
  354. return $this->request('DELETE', $url, $data);
  355. }
  356. public function head(string $url, array $data = [])
  357. {
  358. $this->options[$this->bodyFormat] = $data;
  359. return $this->request('HEAD', $url, $data);
  360. }
  361. public function options(string $url, array $data = [])
  362. {
  363. $this->options[$this->bodyFormat] = $data;
  364. return $this->request('OPTIONS', $url, $data);
  365. }
  366. public function getAsync(string $url, $query = null, callable $success = null, callable $fail = null)
  367. {
  368. is_callable($query) || $this->options['query'] = $query;
  369. return $this->requestAsync('GET', $url, $query, $success, $fail);
  370. }
  371. public function postAsync(string $url, $data = null, callable $success = null, callable $fail = null)
  372. {
  373. is_callable($data) || $this->options[$this->bodyFormat] = $data;
  374. return $this->requestAsync('POST', $url, $data, $success, $fail);
  375. }
  376. public function patchAsync(string $url, $data = null, callable $success = null, callable $fail = null)
  377. {
  378. is_callable($data) || $this->options[$this->bodyFormat] = $data;
  379. return $this->requestAsync('PATCH', $url, $data, $success, $fail);
  380. }
  381. public function putAsync(string $url, $data = null, callable $success = null, callable $fail = null)
  382. {
  383. is_callable($data) || $this->options[$this->bodyFormat] = $data;
  384. return $this->requestAsync('PUT', $url, $data, $success, $fail);
  385. }
  386. public function deleteAsync(string $url, $data = null, callable $success = null, callable $fail = null)
  387. {
  388. is_callable($data) || $this->options[$this->bodyFormat] = $data;
  389. return $this->requestAsync('DELETE', $url, $data, $success, $fail);
  390. }
  391. public function headAsync(string $url, $data = null, callable $success = null, callable $fail = null)
  392. {
  393. is_callable($data) || $this->options[$this->bodyFormat] = $data;
  394. return $this->requestAsync('HEAD', $url, $data, $success, $fail);
  395. }
  396. public function optionsAsync(string $url, $data = null, callable $success = null, callable $fail = null)
  397. {
  398. is_callable($data) || $this->options[$this->bodyFormat] = $data;
  399. return $this->requestAsync('OPTIONS', $url, $data, $success, $fail);
  400. }
  401. public function multiAsync(array $promises, callable $success = null, callable $fail = null)
  402. {
  403. $count = count($promises);
  404. $this->concurrency = $this->concurrency ?: $count;
  405. $requests = function () use ($promises) {
  406. foreach ($promises as $promise) {
  407. yield function () use ($promise) {
  408. return $promise;
  409. };
  410. }
  411. };
  412. $fulfilled = function ($response, $index) use ($success) {
  413. if (!is_null($success)) {
  414. $response = $this->response($response);
  415. call_user_func_array($success, [$response, $index]);
  416. }
  417. };
  418. $rejected = function ($exception, $index) use ($fail) {
  419. if (!is_null($fail)) {
  420. $exception = $this->exception($exception);
  421. call_user_func_array($fail, [$exception, $index]);
  422. }
  423. };
  424. $pool = new Pool($this->client, $requests(), [
  425. 'concurrency' => $this->concurrency,
  426. 'fulfilled' => $fulfilled,
  427. 'rejected' => $rejected,
  428. ]);
  429. $pool->promise();
  430. return $pool;
  431. }
  432. protected function request(string $method, string $url, array $options = [])
  433. {
  434. if (isset($this->options[$this->bodyFormat])) {
  435. $this->options[$this->bodyFormat] = $options;
  436. } else {
  437. $this->options[$this->bodyFormat] = $this->pendingBody;
  438. }
  439. if ($this->isRemoveBodyFormat) {
  440. unset($this->options[$this->bodyFormat]);
  441. }
  442. try {
  443. $response = $this->client->request($method, $url, $this->options);
  444. return $this->response($response);
  445. } catch (ConnectException $e) {
  446. throw new ConnectionException($e->getMessage(), 0, $e);
  447. }
  448. }
  449. /**
  450. * 原生请求
  451. * @param string $method
  452. * @param string $url
  453. * @param array $options
  454. * @return Response
  455. * @throws ConnectionException
  456. * @throws \GuzzleHttp\Exception\GuzzleException
  457. */
  458. public function client(string $method, string $url, array $options = [])
  459. {
  460. if (isset($this->options[$this->bodyFormat])) {
  461. $this->options[$this->bodyFormat] = $options;
  462. } else {
  463. $this->options[$this->bodyFormat] = $this->pendingBody;
  464. }
  465. if ($this->isRemoveBodyFormat) {
  466. unset($this->options[$this->bodyFormat]);
  467. }
  468. try {
  469. if (empty($options)) {
  470. $options = $this->options;
  471. }
  472. $response = $this->client->request($method, $url, $options);
  473. return $this->response($response);
  474. } catch (ConnectException $e) {
  475. throw new ConnectionException($e->getMessage(), 0, $e);
  476. }
  477. }
  478. /**
  479. * 原生异步请求
  480. * @param string $method
  481. * @param string $url
  482. * @param array $options
  483. * @return Response
  484. * @throws ConnectionException
  485. */
  486. public function clientAsync(string $method, string $url, array $options = [])
  487. {
  488. if (isset($this->options[$this->bodyFormat])) {
  489. $this->options[$this->bodyFormat] = $options;
  490. } else {
  491. $this->options[$this->bodyFormat] = $this->pendingBody;
  492. }
  493. if ($this->isRemoveBodyFormat) {
  494. unset($this->options[$this->bodyFormat]);
  495. }
  496. try {
  497. if (empty($options)) {
  498. $options = $this->options;
  499. }
  500. $response = $this->client->requestAsync($method, $url, $options);
  501. return $this->response($response);
  502. } catch (ConnectException $e) {
  503. throw new ConnectionException($e->getMessage(), 0, $e);
  504. }
  505. }
  506. protected function requestAsync(string $method, string $url, $options = null, callable $success = null, callable $fail = null)
  507. {
  508. if (is_callable($options)) {
  509. $successCallback = $options;
  510. $failCallback = $success;
  511. } else {
  512. $successCallback = $success;
  513. $failCallback = $fail;
  514. }
  515. if (isset($this->options[$this->bodyFormat])) {
  516. $this->options[$this->bodyFormat] = $options;
  517. } else {
  518. $this->options[$this->bodyFormat] = $this->pendingBody;
  519. }
  520. if ($this->isRemoveBodyFormat) {
  521. unset($this->options[$this->bodyFormat]);
  522. }
  523. try {
  524. $promise = $this->client->requestAsync($method, $url, $this->options);
  525. $fulfilled = function ($response) use ($successCallback) {
  526. if (!is_null($successCallback)) {
  527. $response = $this->response($response);
  528. call_user_func_array($successCallback, [$response]);
  529. }
  530. };
  531. $rejected = function ($exception) use ($failCallback) {
  532. if (!is_null($failCallback)) {
  533. $exception = $this->exception($exception);
  534. call_user_func_array($failCallback, [$exception]);
  535. }
  536. };
  537. $promise->then($fulfilled, $rejected);
  538. $this->promises[] = $promise;
  539. return $promise;
  540. } catch (ConnectException $e) {
  541. throw new ConnectionException($e->getMessage(), 0, $e);
  542. }
  543. }
  544. public function wait()
  545. {
  546. if (!empty($this->promises)) {
  547. \GuzzleHttp\Promise\Utils($this->promises)->wait();
  548. }
  549. $this->promises = [];
  550. }
  551. protected function response($response)
  552. {
  553. return new Response($response);
  554. }
  555. protected function exception($exception)
  556. {
  557. return new RequestException($exception);
  558. }
  559. }