PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/websocket/vendor/guzzle/http/Guzzle/Http/Message/Request.php

https://github.com/ivebeenlinuxed/Boiler
PHP | 638 lines | 442 code | 107 blank | 89 comment | 57 complexity | 49eeb3d0438ae595faba011d79755220 MD5 | raw file
  1. <?php
  2. namespace Guzzle\Http\Message;
  3. use Guzzle\Common\Version;
  4. use Guzzle\Common\Event;
  5. use Guzzle\Common\Collection;
  6. use Guzzle\Common\Exception\RuntimeException;
  7. use Guzzle\Common\Exception\InvalidArgumentException;
  8. use Guzzle\Http\Exception\RequestException;
  9. use Guzzle\Http\Exception\BadResponseException;
  10. use Guzzle\Http\ClientInterface;
  11. use Guzzle\Http\EntityBody;
  12. use Guzzle\Http\EntityBodyInterface;
  13. use Guzzle\Http\Message\Header\HeaderInterface;
  14. use Guzzle\Http\Url;
  15. use Guzzle\Parser\ParserRegistry;
  16. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  17. use Symfony\Component\EventDispatcher\EventDispatcher;
  18. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  19. /**
  20. * HTTP request class to send requests
  21. */
  22. class Request extends AbstractMessage implements RequestInterface
  23. {
  24. /** @var EventDispatcherInterface */
  25. protected $eventDispatcher;
  26. /** @var Url HTTP Url */
  27. protected $url;
  28. /** @var string HTTP method (GET, PUT, POST, DELETE, HEAD, OPTIONS, TRACE) */
  29. protected $method;
  30. /** @var ClientInterface */
  31. protected $client;
  32. /** @var Response Response of the request */
  33. protected $response;
  34. /** @var EntityBodyInterface Response body */
  35. protected $responseBody;
  36. /** @var string State of the request object */
  37. protected $state;
  38. /** @var string Authentication username */
  39. protected $username;
  40. /** @var string Auth password */
  41. protected $password;
  42. /** @var Collection cURL specific transfer options */
  43. protected $curlOptions;
  44. /** @var bool */
  45. protected $isRedirect = false;
  46. public static function getAllEvents()
  47. {
  48. return array(
  49. // Called when receiving or uploading data through cURL
  50. 'curl.callback.read', 'curl.callback.write', 'curl.callback.progress',
  51. // Cloning a request
  52. 'request.clone',
  53. // About to send the request, sent request, completed transaction
  54. 'request.before_send', 'request.sent', 'request.complete',
  55. // A request received a successful response
  56. 'request.success',
  57. // A request received an unsuccessful response
  58. 'request.error',
  59. // An exception is being thrown because of an unsuccessful response
  60. 'request.exception',
  61. // Received response status line
  62. 'request.receive.status_line'
  63. );
  64. }
  65. /**
  66. * @param string $method HTTP method
  67. * @param string|Url $url HTTP URL to connect to. The URI scheme, host header, and URI are parsed from the
  68. * full URL. If query string parameters are present they will be parsed as well.
  69. * @param array|Collection $headers HTTP headers
  70. */
  71. public function __construct($method, $url, $headers = array())
  72. {
  73. parent::__construct();
  74. $this->method = strtoupper($method);
  75. $this->curlOptions = new Collection();
  76. $this->setUrl($url);
  77. if ($headers) {
  78. // Special handling for multi-value headers
  79. foreach ($headers as $key => $value) {
  80. // Deal with collisions with Host and Authorization
  81. if ($key == 'host' || $key == 'Host') {
  82. $this->setHeader($key, $value);
  83. } elseif ($value instanceof HeaderInterface) {
  84. $this->addHeader($key, $value);
  85. } else {
  86. foreach ((array) $value as $v) {
  87. $this->addHeader($key, $v);
  88. }
  89. }
  90. }
  91. }
  92. $this->setState(self::STATE_NEW);
  93. }
  94. public function __clone()
  95. {
  96. if ($this->eventDispatcher) {
  97. $this->eventDispatcher = clone $this->eventDispatcher;
  98. }
  99. $this->curlOptions = clone $this->curlOptions;
  100. $this->params = clone $this->params;
  101. $this->url = clone $this->url;
  102. $this->response = $this->responseBody = null;
  103. $this->headers = clone $this->headers;
  104. $this->setState(RequestInterface::STATE_NEW);
  105. $this->dispatch('request.clone', array('request' => $this));
  106. }
  107. /**
  108. * Get the HTTP request as a string
  109. *
  110. * @return string
  111. */
  112. public function __toString()
  113. {
  114. return $this->getRawHeaders() . "\r\n\r\n";
  115. }
  116. /**
  117. * Default method that will throw exceptions if an unsuccessful response is received.
  118. *
  119. * @param Event $event Received
  120. * @throws BadResponseException if the response is not successful
  121. */
  122. public static function onRequestError(Event $event)
  123. {
  124. $e = BadResponseException::factory($event['request'], $event['response']);
  125. $event['request']->setState(self::STATE_ERROR, array('exception' => $e) + $event->toArray());
  126. throw $e;
  127. }
  128. public function setClient(ClientInterface $client)
  129. {
  130. $this->client = $client;
  131. return $this;
  132. }
  133. public function getClient()
  134. {
  135. return $this->client;
  136. }
  137. public function getRawHeaders()
  138. {
  139. $protocolVersion = $this->protocolVersion ?: '1.1';
  140. return trim($this->method . ' ' . $this->getResource()) . ' '
  141. . strtoupper(str_replace('https', 'http', $this->url->getScheme()))
  142. . '/' . $protocolVersion . "\r\n" . implode("\r\n", $this->getHeaderLines());
  143. }
  144. public function setUrl($url)
  145. {
  146. if ($url instanceof Url) {
  147. $this->url = $url;
  148. } else {
  149. $this->url = Url::factory($url);
  150. }
  151. // Update the port and host header
  152. $this->setPort($this->url->getPort());
  153. if ($this->url->getUsername() || $this->url->getPassword()) {
  154. $this->setAuth($this->url->getUsername(), $this->url->getPassword());
  155. // Remove the auth info from the URL
  156. $this->url->setUsername(null);
  157. $this->url->setPassword(null);
  158. }
  159. return $this;
  160. }
  161. public function send()
  162. {
  163. if (!$this->client) {
  164. throw new RuntimeException('A client must be set on the request');
  165. }
  166. return $this->client->send($this);
  167. }
  168. public function getResponse()
  169. {
  170. return $this->response;
  171. }
  172. public function getQuery($asString = false)
  173. {
  174. return $asString
  175. ? (string) $this->url->getQuery()
  176. : $this->url->getQuery();
  177. }
  178. public function getMethod()
  179. {
  180. return $this->method;
  181. }
  182. public function getScheme()
  183. {
  184. return $this->url->getScheme();
  185. }
  186. public function setScheme($scheme)
  187. {
  188. $this->url->setScheme($scheme);
  189. return $this;
  190. }
  191. public function getHost()
  192. {
  193. return $this->url->getHost();
  194. }
  195. public function setHost($host)
  196. {
  197. $this->url->setHost($host);
  198. $this->setPort($this->url->getPort());
  199. return $this;
  200. }
  201. public function getProtocolVersion()
  202. {
  203. return $this->protocolVersion;
  204. }
  205. public function setProtocolVersion($protocol)
  206. {
  207. $this->protocolVersion = $protocol;
  208. return $this;
  209. }
  210. public function getPath()
  211. {
  212. return '/' . ltrim($this->url->getPath(), '/');
  213. }
  214. public function setPath($path)
  215. {
  216. $this->url->setPath($path);
  217. return $this;
  218. }
  219. public function getPort()
  220. {
  221. return $this->url->getPort();
  222. }
  223. public function setPort($port)
  224. {
  225. $this->url->setPort($port);
  226. // Include the port in the Host header if it is not the default port for the scheme of the URL
  227. $scheme = $this->url->getScheme();
  228. if (($scheme == 'http' && $port != 80) || ($scheme == 'https' && $port != 443)) {
  229. $this->headers['host'] = $this->headerFactory->createHeader('Host', $this->url->getHost() . ':' . $port);
  230. } else {
  231. $this->headers['host'] = $this->headerFactory->createHeader('Host', $this->url->getHost());
  232. }
  233. return $this;
  234. }
  235. public function getUsername()
  236. {
  237. return $this->username;
  238. }
  239. public function getPassword()
  240. {
  241. return $this->password;
  242. }
  243. public function setAuth($user, $password = '', $scheme = CURLAUTH_BASIC)
  244. {
  245. static $authMap = array(
  246. 'basic' => CURLAUTH_BASIC,
  247. 'digest' => CURLAUTH_DIGEST,
  248. 'ntlm' => CURLAUTH_NTLM,
  249. 'any' => CURLAUTH_ANY
  250. );
  251. // If we got false or null, disable authentication
  252. if (!$user) {
  253. $this->password = $this->username = null;
  254. $this->removeHeader('Authorization');
  255. $this->getCurlOptions()->remove(CURLOPT_HTTPAUTH);
  256. return $this;
  257. }
  258. if (!is_numeric($scheme)) {
  259. $scheme = strtolower($scheme);
  260. if (!isset($authMap[$scheme])) {
  261. throw new InvalidArgumentException($scheme . ' is not a valid authentication type');
  262. }
  263. $scheme = $authMap[$scheme];
  264. }
  265. $this->username = $user;
  266. $this->password = $password;
  267. // Bypass CURL when using basic auth to promote connection reuse
  268. if ($scheme == CURLAUTH_BASIC) {
  269. $this->getCurlOptions()->remove(CURLOPT_HTTPAUTH);
  270. $this->setHeader('Authorization', 'Basic ' . base64_encode($this->username . ':' . $this->password));
  271. } else {
  272. $this->getCurlOptions()
  273. ->set(CURLOPT_HTTPAUTH, $scheme)
  274. ->set(CURLOPT_USERPWD, $this->username . ':' . $this->password);
  275. }
  276. return $this;
  277. }
  278. public function getResource()
  279. {
  280. $resource = $this->getPath();
  281. if ($query = (string) $this->url->getQuery()) {
  282. $resource .= '?' . $query;
  283. }
  284. return $resource;
  285. }
  286. public function getUrl($asObject = false)
  287. {
  288. return $asObject ? clone $this->url : (string) $this->url;
  289. }
  290. public function getState()
  291. {
  292. return $this->state;
  293. }
  294. public function setState($state, array $context = array())
  295. {
  296. $oldState = $this->state;
  297. $this->state = $state;
  298. switch ($state) {
  299. case self::STATE_NEW:
  300. $this->response = null;
  301. break;
  302. case self::STATE_TRANSFER:
  303. if ($oldState !== $state) {
  304. // Fix Content-Length and Transfer-Encoding collisions
  305. if ($this->hasHeader('Transfer-Encoding') && $this->hasHeader('Content-Length')) {
  306. $this->removeHeader('Transfer-Encoding');
  307. }
  308. $this->dispatch('request.before_send', array('request' => $this));
  309. }
  310. break;
  311. case self::STATE_COMPLETE:
  312. if ($oldState !== $state) {
  313. $this->processResponse($context);
  314. $this->responseBody = null;
  315. }
  316. break;
  317. case self::STATE_ERROR:
  318. if (isset($context['exception'])) {
  319. $this->dispatch('request.exception', array(
  320. 'request' => $this,
  321. 'response' => isset($context['response']) ? $context['response'] : $this->response,
  322. 'exception' => isset($context['exception']) ? $context['exception'] : null
  323. ));
  324. }
  325. }
  326. return $this->state;
  327. }
  328. public function getCurlOptions()
  329. {
  330. return $this->curlOptions;
  331. }
  332. public function startResponse(Response $response)
  333. {
  334. $this->state = self::STATE_TRANSFER;
  335. $response->setEffectiveUrl((string) $this->getUrl());
  336. $this->response = $response;
  337. return $this;
  338. }
  339. public function setResponse(Response $response, $queued = false)
  340. {
  341. $response->setEffectiveUrl((string) $this->url);
  342. if ($queued) {
  343. $ed = $this->getEventDispatcher();
  344. $ed->addListener('request.before_send', $f = function ($e) use ($response, &$f, $ed) {
  345. $e['request']->setResponse($response);
  346. $ed->removeListener('request.before_send', $f);
  347. }, -9999);
  348. } else {
  349. $this->response = $response;
  350. // If a specific response body is specified, then use it instead of the response's body
  351. if ($this->responseBody && !$this->responseBody->getCustomData('default') && !$response->isRedirect()) {
  352. $this->getResponseBody()->write((string) $this->response->getBody());
  353. } else {
  354. $this->responseBody = $this->response->getBody();
  355. }
  356. $this->setState(self::STATE_COMPLETE);
  357. }
  358. return $this;
  359. }
  360. public function setResponseBody($body)
  361. {
  362. // Attempt to open a file for writing if a string was passed
  363. if (is_string($body)) {
  364. // @codeCoverageIgnoreStart
  365. if (!($body = fopen($body, 'w+'))) {
  366. throw new InvalidArgumentException('Could not open ' . $body . ' for writing');
  367. }
  368. // @codeCoverageIgnoreEnd
  369. }
  370. $this->responseBody = EntityBody::factory($body);
  371. return $this;
  372. }
  373. public function getResponseBody()
  374. {
  375. if ($this->responseBody === null) {
  376. $this->responseBody = EntityBody::factory()->setCustomData('default', true);
  377. }
  378. return $this->responseBody;
  379. }
  380. /**
  381. * Determine if the response body is repeatable (readable + seekable)
  382. *
  383. * @return bool
  384. * @deprecated Use getResponseBody()->isSeekable()
  385. * @codeCoverageIgnore
  386. */
  387. public function isResponseBodyRepeatable()
  388. {
  389. Version::warn(__METHOD__ . ' is deprecated. Use $request->getResponseBody()->isRepeatable()');
  390. return !$this->responseBody ? true : $this->responseBody->isRepeatable();
  391. }
  392. public function getCookies()
  393. {
  394. if ($cookie = $this->getHeader('Cookie')) {
  395. $data = ParserRegistry::getInstance()->getParser('cookie')->parseCookie($cookie);
  396. return $data['cookies'];
  397. }
  398. return array();
  399. }
  400. public function getCookie($name)
  401. {
  402. $cookies = $this->getCookies();
  403. return isset($cookies[$name]) ? $cookies[$name] : null;
  404. }
  405. public function addCookie($name, $value)
  406. {
  407. if (!$this->hasHeader('Cookie')) {
  408. $this->setHeader('Cookie', "{$name}={$value}");
  409. } else {
  410. $this->getHeader('Cookie')->add("{$name}={$value}");
  411. }
  412. // Always use semicolons to separate multiple cookie headers
  413. $this->getHeader('Cookie')->setGlue(';');
  414. return $this;
  415. }
  416. public function removeCookie($name)
  417. {
  418. if ($cookie = $this->getHeader('Cookie')) {
  419. foreach ($cookie as $cookieValue) {
  420. if (strpos($cookieValue, $name . '=') === 0) {
  421. $cookie->removeValue($cookieValue);
  422. }
  423. }
  424. }
  425. return $this;
  426. }
  427. public function setEventDispatcher(EventDispatcherInterface $eventDispatcher)
  428. {
  429. $this->eventDispatcher = $eventDispatcher;
  430. $this->eventDispatcher->addListener('request.error', array(__CLASS__, 'onRequestError'), -255);
  431. return $this;
  432. }
  433. public function getEventDispatcher()
  434. {
  435. if (!$this->eventDispatcher) {
  436. $this->setEventDispatcher(new EventDispatcher());
  437. }
  438. return $this->eventDispatcher;
  439. }
  440. public function dispatch($eventName, array $context = array())
  441. {
  442. $context['request'] = $this;
  443. return $this->getEventDispatcher()->dispatch($eventName, new Event($context));
  444. }
  445. public function addSubscriber(EventSubscriberInterface $subscriber)
  446. {
  447. $this->getEventDispatcher()->addSubscriber($subscriber);
  448. return $this;
  449. }
  450. /**
  451. * Get an array containing the request and response for event notifications
  452. *
  453. * @return array
  454. */
  455. protected function getEventArray()
  456. {
  457. return array(
  458. 'request' => $this,
  459. 'response' => $this->response
  460. );
  461. }
  462. /**
  463. * Process a received response
  464. *
  465. * @param array $context Contextual information
  466. * @throws RequestException|BadResponseException on unsuccessful responses
  467. */
  468. protected function processResponse(array $context = array())
  469. {
  470. if (!$this->response) {
  471. // If no response, then processResponse shouldn't have been called
  472. $e = new RequestException('Error completing request');
  473. $e->setRequest($this);
  474. throw $e;
  475. }
  476. $this->state = self::STATE_COMPLETE;
  477. // A request was sent, but we don't know if we'll send more or if the final response will be successful
  478. $this->dispatch('request.sent', $this->getEventArray() + $context);
  479. // Some response processors will remove the response or reset the state (example: ExponentialBackoffPlugin)
  480. if ($this->state == RequestInterface::STATE_COMPLETE) {
  481. // The request completed, so the HTTP transaction is complete
  482. $this->dispatch('request.complete', $this->getEventArray());
  483. // If the response is bad, allow listeners to modify it or throw exceptions. You can change the response by
  484. // modifying the Event object in your listeners or calling setResponse() on the request
  485. if ($this->response->isError()) {
  486. $event = new Event($this->getEventArray());
  487. $this->getEventDispatcher()->dispatch('request.error', $event);
  488. // Allow events of request.error to quietly change the response
  489. if ($event['response'] !== $this->response) {
  490. $this->response = $event['response'];
  491. }
  492. }
  493. // If a successful response was received, dispatch an event
  494. if ($this->response->isSuccessful()) {
  495. $this->dispatch('request.success', $this->getEventArray());
  496. }
  497. }
  498. }
  499. /**
  500. * @deprecated Use Guzzle\Plugin\Cache\DefaultCanCacheStrategy
  501. * @codeCoverageIgnore
  502. */
  503. public function canCache()
  504. {
  505. Version::warn(__METHOD__ . ' is deprecated. Use Guzzle\Plugin\Cache\DefaultCanCacheStrategy.');
  506. if (class_exists('Guzzle\Plugin\Cache\DefaultCanCacheStrategy')) {
  507. $canCache = new \Guzzle\Plugin\Cache\DefaultCanCacheStrategy();
  508. return $canCache->canCacheRequest($this);
  509. } else {
  510. return false;
  511. }
  512. }
  513. /**
  514. * @deprecated Use the history plugin (not emitting a warning as this is built-into the RedirectPlugin for now)
  515. * @codeCoverageIgnore
  516. */
  517. public function setIsRedirect($isRedirect)
  518. {
  519. $this->isRedirect = $isRedirect;
  520. return $this;
  521. }
  522. /**
  523. * @deprecated Use the history plugin
  524. * @codeCoverageIgnore
  525. */
  526. public function isRedirect()
  527. {
  528. Version::warn(__METHOD__ . ' is deprecated. Use the HistoryPlugin to track this.');
  529. return $this->isRedirect;
  530. }
  531. }