Overview

Namespaces

  • Contrib
    • Bundle
      • CoverallsBundle
        • Console
        • Entity
      • CoverallsV1Bundle
        • Api
        • Collector
        • Command
        • Config
        • Entity
          • Git
    • Component
      • File
      • Log
      • System
        • Git
  • Guzzle
    • Batch
      • Exception
    • Cache
    • Common
      • Exception
    • Http
      • Curl
      • Exception
      • Message
      • QueryAggregator
    • Inflection
    • Iterator
    • Log
    • Parser
      • Cookie
      • Message
      • UriTemplate
      • Url
    • Plugin
      • Async
      • Backoff
      • Cache
      • Cookie
        • CookieJar
        • Exception
      • CurlAuth
      • ErrorResponse
        • Exception
      • History
      • Log
      • Md5
      • Mock
      • Oauth
    • Service
      • Builder
      • Command
        • Factory
        • LocationVisitor
          • Request
          • Response
      • Description
      • Exception
      • Resource
    • Stream
  • PHP
  • Psr
    • Log
  • Symfony
    • Component
      • Config
        • Definition
          • Builder
          • Exception
        • Exception
        • Loader
        • Resource
        • Util
      • Console
        • Command
        • Formatter
        • Helper
        • Input
        • Output
        • Tester
      • EventDispatcher
        • Debug
      • Finder
        • Adapter
        • Comparator
        • Exception
        • Expression
        • Iterator
        • Shell
      • Stopwatch
      • Yaml
        • Exception

Classes

  • AbstractMessage
  • EntityEnclosingRequest
  • Header
  • HeaderComparison
  • PostFile
  • Request
  • RequestFactory
  • Response

Interfaces

  • EntityEnclosingRequestInterface
  • MessageInterface
  • PostFileInterface
  • RequestFactoryInterface
  • RequestInterface
  • Overview
  • Namespace
  • Class
  • Tree
  • Todo
  1: <?php
  2: 
  3: namespace Guzzle\Http\Message;
  4: 
  5: use Guzzle\Common\Event;
  6: use Guzzle\Common\Collection;
  7: use Guzzle\Common\Exception\RuntimeException;
  8: use Guzzle\Common\Exception\InvalidArgumentException;
  9: use Guzzle\Http\Exception\RequestException;
 10: use Guzzle\Http\Exception\BadResponseException;
 11: use Guzzle\Http\ClientInterface;
 12: use Guzzle\Http\EntityBody;
 13: use Guzzle\Http\EntityBodyInterface;
 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: /**
 21:  * HTTP request class to send requests
 22:  */
 23: class Request extends AbstractMessage implements RequestInterface
 24: {
 25:     /**
 26:      * @var EventDispatcherInterface
 27:      */
 28:     protected $eventDispatcher;
 29: 
 30:     /**
 31:      * @var Url HTTP Url
 32:      */
 33:     protected $url;
 34: 
 35:     /**
 36:      * @var string HTTP method (GET, PUT, POST, DELETE, HEAD, OPTIONS, TRACE)
 37:      */
 38:     protected $method;
 39: 
 40:     /**
 41:      * @var ClientInterface
 42:      */
 43:     protected $client;
 44: 
 45:     /**
 46:      * @var Response Response of the request
 47:      */
 48:     protected $response;
 49: 
 50:     /**
 51:      * @var EntityBodyInterface Response body
 52:      */
 53:     protected $responseBody;
 54: 
 55:     /**
 56:      * @var string State of the request object
 57:      */
 58:     protected $state;
 59: 
 60:     /**
 61:      * @var string Authentication username
 62:      */
 63:     protected $username;
 64: 
 65:     /**
 66:      * @var string Auth password
 67:      */
 68:     protected $password;
 69: 
 70:     /**
 71:      * @var Collection cURL specific transfer options
 72:      */
 73:     protected $curlOptions;
 74: 
 75:     /**
 76:      * @var bool
 77:      */
 78:     protected $isRedirect = false;
 79: 
 80:     /**
 81:      * {@inheritdoc}
 82:      */
 83:     public static function getAllEvents()
 84:     {
 85:         return array(
 86:             // Called when receiving or uploading data through cURL
 87:             'curl.callback.read', 'curl.callback.write', 'curl.callback.progress',
 88:             // Cloning a request
 89:             'request.clone',
 90:             // About to send the request, sent request, completed transaction
 91:             'request.before_send', 'request.sent', 'request.complete',
 92:             // A request received a successful response
 93:             'request.success',
 94:             // A request received an unsuccessful response
 95:             'request.error',
 96:             // An exception is being thrown because of an unsuccessful response
 97:             'request.exception',
 98:             // Received response status line
 99:             'request.receive.status_line',
100:             // Manually set a response
101:             'request.set_response'
102:         );
103:     }
104: 
105:     /**
106:      * Create a new request
107:      *
108:      * @param string           $method  HTTP method
109:      * @param string|Url       $url     HTTP URL to connect to. The URI scheme, host header, and URI are parsed from the
110:      *                                  full URL. If query string parameters are present they will be parsed as well.
111:      * @param array|Collection $headers HTTP headers
112:      */
113:     public function __construct($method, $url, $headers = array())
114:     {
115:         $this->method = strtoupper($method);
116:         $this->curlOptions = new Collection();
117:         $this->params = new Collection();
118:         $this->setUrl($url);
119: 
120:         if ($headers) {
121:             // Special handling for multi-value headers
122:             foreach ($headers as $key => $value) {
123:                 $lkey = strtolower($key);
124:                 // Deal with collisions with Host and Authorization
125:                 if ($lkey == 'host') {
126:                     $this->setHeader($key, $value);
127:                 } elseif ($lkey == 'authorization') {
128:                     $parts = explode(' ', $value);
129:                     if ($parts[0] == 'Basic' && isset($parts[1])) {
130:                         list($user, $pass) = explode(':', base64_decode($parts[1]));
131:                         $this->setAuth($user, $pass);
132:                     } else {
133:                         $this->setHeader($key, $value);
134:                     }
135:                 } else {
136:                     foreach ((array) $value as $v) {
137:                         $this->addHeader($key, $v);
138:                     }
139:                 }
140:             }
141:         }
142: 
143:         $this->setState(self::STATE_NEW);
144:     }
145: 
146:     /**
147:      * Clone the request object, leaving off any response that was received
148:      * @see Guzzle\Plugin\Redirect\RedirectPlugin::cloneRequestWithGetMethod
149:      */
150:     public function __clone()
151:     {
152:         if ($this->eventDispatcher) {
153:             $this->eventDispatcher = clone $this->eventDispatcher;
154:         }
155:         $this->curlOptions = clone $this->curlOptions;
156:         $this->params = clone $this->params;
157:         // Remove state based parameters from the cloned request
158:         $this->params->remove('curl_handle')->remove('curl_multi');
159:         $this->url = clone $this->url;
160:         $this->response = $this->responseBody = null;
161: 
162:         // Clone each header
163:         foreach ($this->headers as $key => &$value) {
164:             $value = clone $value;
165:         }
166: 
167:         $this->setState(RequestInterface::STATE_NEW);
168:         $this->dispatch('request.clone', array('request' => $this));
169:     }
170: 
171:     /**
172:      * Get the HTTP request as a string
173:      *
174:      * @return string
175:      */
176:     public function __toString()
177:     {
178:         return $this->getRawHeaders() . "\r\n\r\n";
179:     }
180: 
181:     /**
182:      * Default method that will throw exceptions if an unsuccessful response
183:      * is received.
184:      *
185:      * @param Event $event Received
186:      * @throws BadResponseException if the response is not successful
187:      */
188:     public static function onRequestError(Event $event)
189:     {
190:         $e = BadResponseException::factory($event['request'], $event['response']);
191:         $event['request']->dispatch('request.exception', array(
192:             'request'   => $event['request'],
193:             'response'  => $event['response'],
194:             'exception' => $e
195:         ));
196: 
197:         throw $e;
198:     }
199: 
200:     /**
201:      * {@inheritdoc}
202:      */
203:     public function setClient(ClientInterface $client)
204:     {
205:         $this->client = $client;
206: 
207:         return $this;
208:     }
209: 
210:     /**
211:      * {@inheritdoc}
212:      */
213:     public function getClient()
214:     {
215:         return $this->client;
216:     }
217: 
218:     /**
219:      * {@inheritdoc}
220:      */
221:     public function getRawHeaders()
222:     {
223:         $protocolVersion = $this->protocolVersion ?: '1.1';
224: 
225:         return trim($this->method . ' ' . $this->getResource()) . ' '
226:             . strtoupper(str_replace('https', 'http', $this->url->getScheme()))
227:             . '/' . $protocolVersion . "\r\n" . implode("\r\n", $this->getHeaderLines());
228:     }
229: 
230:     /**
231:      * {@inheritdoc}
232:      */
233:     public function setUrl($url)
234:     {
235:         if ($url instanceof Url) {
236:             $this->url = $url;
237:         } else {
238:             $this->url = Url::factory($url);
239:         }
240: 
241:         // Update the port and host header
242:         $this->setPort($this->url->getPort());
243: 
244:         if ($this->url->getUsername() || $this->url->getPassword()) {
245:             $this->setAuth($this->url->getUsername(), $this->url->getPassword());
246:             // Remove the auth info from the URL
247:             $this->url->setUsername(null);
248:             $this->url->setPassword(null);
249:         }
250: 
251:         return $this;
252:     }
253: 
254:     /**
255:      * {@inheritdoc}
256:      */
257:     public function send()
258:     {
259:         if (!$this->client) {
260:             throw new RuntimeException('A client must be set on the request');
261:         }
262: 
263:         return $this->client->send($this);
264:     }
265: 
266:     /**
267:      * {@inheritdoc}
268:      */
269:     public function getResponse()
270:     {
271:         return $this->response;
272:     }
273: 
274:     /**
275:      * {@inheritdoc}
276:      */
277:     public function getQuery($asString = false)
278:     {
279:         return $asString
280:             ? (string) $this->url->getQuery()
281:             : $this->url->getQuery();
282:     }
283: 
284:     /**
285:      * {@inheritdoc}
286:      */
287:     public function getMethod()
288:     {
289:         return $this->method;
290:     }
291: 
292:     /**
293:      * {@inheritdoc}
294:      */
295:     public function getScheme()
296:     {
297:         return $this->url->getScheme();
298:     }
299: 
300:     /**
301:      * {@inheritdoc}
302:      */
303:     public function setScheme($scheme)
304:     {
305:         $this->url->setScheme($scheme);
306: 
307:         return $this;
308:     }
309: 
310:     /**
311:      * {@inheritdoc}
312:      */
313:     public function getHost()
314:     {
315:         return $this->url->getHost();
316:     }
317: 
318:     /**
319:      * {@inheritdoc}
320:      */
321:     public function setHost($host)
322:     {
323:         $this->url->setHost($host);
324:         $this->setPort($this->url->getPort());
325: 
326:         return $this;
327:     }
328: 
329:     /**
330:      * {@inheritdoc}
331:      */
332:     public function getProtocolVersion()
333:     {
334:         return $this->protocolVersion;
335:     }
336: 
337:     /**
338:      * {@inheritdoc}
339:      */
340:     public function setProtocolVersion($protocol)
341:     {
342:         $this->protocolVersion = $protocol;
343: 
344:         return $this;
345:     }
346: 
347:     /**
348:      * {@inheritdoc}
349:      */
350:     public function getPath()
351:     {
352:         return '/' . ltrim($this->url->getPath(), '/');
353:     }
354: 
355:     /**
356:      * {@inheritdoc}
357:      */
358:     public function setPath($path)
359:     {
360:         $this->url->setPath($path);
361: 
362:         return $this;
363:     }
364: 
365:     /**
366:      * {@inheritdoc}
367:      */
368:     public function getPort()
369:     {
370:         return $this->url->getPort();
371:     }
372: 
373:     /**
374:      * {@inheritdoc}
375:      */
376:     public function setPort($port)
377:     {
378:         $this->url->setPort($port);
379: 
380:         // Include the port in the Host header if it is not the default port for the scheme of the URL
381:         $scheme = $this->url->getScheme();
382:         if (($scheme == 'http' && $port != 80) || ($scheme == 'https' && $port != 443)) {
383:             $this->headers['host'] = new Header('Host', $this->url->getHost() . ':' . $port);
384:         } else {
385:             $this->headers['host'] = new Header('Host', $this->url->getHost());
386:         }
387: 
388:         return $this;
389:     }
390: 
391:     /**
392:      * {@inheritdoc}
393:      */
394:     public function getUsername()
395:     {
396:         return $this->username;
397:     }
398: 
399:     /**
400:      * {@inheritdoc}
401:      */
402:     public function getPassword()
403:     {
404:         return $this->password;
405:     }
406: 
407:     /**
408:      * {@inheritdoc}
409:      */
410:     public function setAuth($user, $password = '', $scheme = CURLAUTH_BASIC)
411:     {
412:         // If we got false or null, disable authentication
413:         if (!$user) {
414:             $this->password = $this->username = null;
415:             $this->removeHeader('Authorization');
416:             $this->getCurlOptions()->remove(CURLOPT_HTTPAUTH);
417:         } else {
418:             $this->username = $user;
419:             $this->password = $password;
420:             // Bypass CURL when using basic auth to promote connection reuse
421:             if ($scheme == CURLAUTH_BASIC) {
422:                 $this->getCurlOptions()->remove(CURLOPT_HTTPAUTH);
423:                 $this->setHeader('Authorization', 'Basic ' . base64_encode($this->username . ':' . $this->password));
424:             } else {
425:                 $this->getCurlOptions()
426:                     ->set(CURLOPT_HTTPAUTH, $scheme)
427:                     ->set(CURLOPT_USERPWD, $this->username . ':' . $this->password);
428:             }
429:         }
430: 
431:         return $this;
432:     }
433: 
434:     /**
435:      * {@inheritdoc}
436:      */
437:     public function getResource()
438:     {
439:         $resource = $this->getPath();
440:         if ($query = (string) $this->url->getQuery()) {
441:             $resource .= '?' . $query;
442:         }
443: 
444:         return $resource;
445:     }
446: 
447:     /**
448:      * {@inheritdoc}
449:      */
450:     public function getUrl($asObject = false)
451:     {
452:         return $asObject ? clone $this->url : (string) $this->url;
453:     }
454: 
455:     /**
456:      * {@inheritdoc}
457:      */
458:     public function getState()
459:     {
460:         return $this->state;
461:     }
462: 
463:     /**
464:      * {@inheritdoc}
465:      */
466:     public function setState($state, array $context = array())
467:     {
468:         $this->state = $state;
469:         if ($this->state == self::STATE_NEW) {
470:             $this->response = null;
471:         } elseif ($this->state == self::STATE_COMPLETE) {
472:             $this->processResponse($context);
473:             $this->responseBody = null;
474:         }
475: 
476:         return $this;
477:     }
478: 
479:     /**
480:      * {@inheritdoc}
481:      */
482:     public function getCurlOptions()
483:     {
484:         return $this->curlOptions;
485:     }
486: 
487:     /**
488:      * {@inheritdoc}
489:      */
490:     public function receiveResponseHeader($data)
491:     {
492:         static $normalize = array("\r", "\n");
493:         $this->state = self::STATE_TRANSFER;
494:         $length = strlen($data);
495:         $data = str_replace($normalize, '', $data);
496: 
497:         if (strpos($data, 'HTTP/') === 0) {
498: 
499:             $startLine = explode(' ', $data, 3);
500:             $code = $startLine[1];
501:             $status = isset($startLine[2]) ? $startLine[2] : '';
502: 
503:             // Only download the body of the response to the specified response
504:             // body when a successful response is received.
505:             $body = $code >= 200 && $code < 300 ? $this->getResponseBody() : EntityBody::factory();
506: 
507:             $this->response = new Response($code, null, $body);
508:             $this->response->setStatus($code, $status)->setRequest($this);
509:             $this->dispatch('request.receive.status_line', array(
510:                 'request'       => $this,
511:                 'line'          => $data,
512:                 'status_code'   => $code,
513:                 'reason_phrase' => $status
514:             ));
515: 
516:         } elseif (strpos($data, ':') !== false) {
517: 
518:             list($header, $value) = explode(':', $data, 2);
519:             $this->response->addHeader(trim($header), trim($value));
520:         }
521: 
522:         return $length;
523:     }
524: 
525:     /**
526:      * {@inheritdoc}
527:      */
528:     public function setResponse(Response $response, $queued = false)
529:     {
530:         // Never overwrite the request associated with the response (useful for redirect history)
531:         if (!$response->getRequest()) {
532:             $response->setRequest($this);
533:         }
534: 
535:         if ($queued) {
536:             $this->getEventDispatcher()->addListener('request.before_send', function ($e) use ($response) {
537:                 $e['request']->setResponse($response);
538:             }, -9999);
539:         } else {
540:             $this->response = $response;
541:             // If a specific response body is specified, then use it instead of the response's body
542:             if ($this->responseBody && !$this->responseBody->getCustomData('default')) {
543:                 $this->getResponseBody()->write((string) $this->response->getBody());
544:             } else {
545:                 $this->responseBody = $this->response->getBody();
546:             }
547:             $this->processResponse();
548:         }
549: 
550:         return $this;
551:     }
552: 
553:     /**
554:      * {@inheritdoc}
555:      */
556:     public function setResponseBody($body)
557:     {
558:         // Attempt to open a file for writing if a string was passed
559:         if (is_string($body)) {
560:             // @codeCoverageIgnoreStart
561:             if (!($body = fopen($body, 'w+'))) {
562:                 throw new InvalidArgumentException('Could not open ' . $body . ' for writing');
563:             }
564:             // @codeCoverageIgnoreEnd
565:         }
566: 
567:         $this->responseBody = EntityBody::factory($body);
568: 
569:         return $this;
570:     }
571: 
572:     /**
573:      * {@inheritdoc}
574:      */
575:     public function getResponseBody()
576:     {
577:         if ($this->responseBody === null) {
578:             $this->responseBody = EntityBody::factory();
579:             $this->responseBody->setCustomData('default', true);
580:         }
581: 
582:         return $this->responseBody;
583:     }
584: 
585:     /**
586:      * {@inheritdoc}
587:      */
588:     public function isResponseBodyRepeatable()
589:     {
590:         return !$this->responseBody ? true : $this->responseBody->isSeekable() && $this->responseBody->isReadable();
591:     }
592: 
593:     /**
594:      * {@inheritdoc}
595:      */
596:     public function getCookies()
597:     {
598:         if ($cookie = $this->getHeader('Cookie')) {
599:             $data = ParserRegistry::getInstance()->getParser('cookie')->parseCookie($cookie);
600:             return $data['cookies'];
601:         }
602: 
603:         return array();
604:     }
605: 
606:     /**
607:      * {@inheritdoc}
608:      */
609:     public function getCookie($name)
610:     {
611:         $cookies = $this->getCookies();
612: 
613:         return isset($cookies[$name]) ? $cookies[$name] : null;
614:     }
615: 
616:     /**
617:      * {@inheritdoc}
618:      */
619:     public function addCookie($name, $value)
620:     {
621:         if (!$this->hasHeader('Cookie')) {
622:             $this->setHeader('Cookie', "{$name}={$value}");
623:         } else {
624:             $this->getHeader('Cookie')->add("{$name}={$value}");
625:         }
626: 
627:         // Always use semicolons to separate multiple cookie headers
628:         $this->getHeader('Cookie')->setGlue('; ');
629: 
630:         return $this;
631:     }
632: 
633:     /**
634:      * {@inheritdoc}
635:      */
636:     public function removeCookie($name)
637:     {
638:         if ($cookie = $this->getHeader('Cookie')) {
639:             foreach ($cookie as $cookieValue) {
640:                 if (strpos($cookieValue, $name . '=') === 0) {
641:                     $cookie->removeValue($cookieValue);
642:                 }
643:             }
644:         }
645: 
646:         return $this;
647:     }
648: 
649:     /**
650:      * {@inheritdoc}
651:      */
652:     public function canCache()
653:     {
654:         // Only GET and HEAD requests can be cached
655:         if ($this->method != RequestInterface::GET && $this->method != RequestInterface::HEAD) {
656:             return false;
657:         }
658: 
659:         // Never cache requests when using no-store
660:         if ($this->hasCacheControlDirective('no-store')) {
661:             return false;
662:         }
663: 
664:         return true;
665:     }
666: 
667:     /**
668:      * {@inheritdoc}
669:      */
670:     public function setEventDispatcher(EventDispatcherInterface $eventDispatcher)
671:     {
672:         $this->eventDispatcher = $eventDispatcher;
673:         $this->eventDispatcher->addListener('request.error', array(__CLASS__, 'onRequestError'), -255);
674: 
675:         return $this;
676:     }
677: 
678:     /**
679:      * {@inheritdoc}
680:      */
681:     public function getEventDispatcher()
682:     {
683:         if (!$this->eventDispatcher) {
684:             $this->setEventDispatcher(new EventDispatcher());
685:         }
686: 
687:         return $this->eventDispatcher;
688:     }
689: 
690:     /**
691:      * {@inheritdoc}
692:      */
693:     public function dispatch($eventName, array $context = array())
694:     {
695:         $context['request'] = $this;
696:         $this->getEventDispatcher()->dispatch($eventName, new Event($context));
697:     }
698: 
699:     /**
700:      * {@inheritdoc}
701:      * @codeCoverageIgnore
702:      */
703:     public function addSubscriber(EventSubscriberInterface $subscriber)
704:     {
705:         $this->getEventDispatcher()->addSubscriber($subscriber);
706: 
707:         return $this;
708:     }
709: 
710:     /**
711:      * {@inheritdoc}
712:      */
713:     public function setIsRedirect($isRedirect)
714:     {
715:         $this->isRedirect = $isRedirect;
716: 
717:         return $this;
718:     }
719: 
720:     /**
721:      * {@inheritdoc}
722:      */
723:     public function isRedirect()
724:     {
725:         return $this->isRedirect;
726:     }
727: 
728:     /**
729:      * {@inheritdoc}
730:      */
731:     protected function changedHeader($header)
732:     {
733:         parent::changedHeader($header);
734: 
735:         if ($header == 'host') {
736:             // If the Host header was changed, be sure to update the internal URL
737:             $this->setHost((string) $this->getHeader('Host'));
738:         }
739:     }
740: 
741:     /**
742:      * Get an array containing the request and response for event notifications
743:      *
744:      * @return array
745:      */
746:     protected function getEventArray()
747:     {
748:         return array(
749:             'request'  => $this,
750:             'response' => $this->response
751:         );
752:     }
753: 
754:     /**
755:      * Process a received response
756:      *
757:      * @param array $context Contextual information
758:      * @throws RequestException|BadResponseException on unsuccessful responses
759:      */
760:     protected function processResponse(array $context = array())
761:     {
762:         if (!$this->response) {
763:             // If no response, then processResponse shouldn't have been called
764:             $e = new RequestException('Error completing request');
765:             $e->setRequest($this);
766:             throw $e;
767:         }
768: 
769:         $this->state = self::STATE_COMPLETE;
770: 
771:         // A request was sent, but we don't know if we'll send more or if the final response will be successful
772:         $this->dispatch('request.sent', $this->getEventArray() + $context);
773: 
774:         // Some response processors will remove the response or reset the state (example: ExponentialBackoffPlugin)
775:         if ($this->state == RequestInterface::STATE_COMPLETE) {
776: 
777:             // The request completed, so the HTTP transaction is complete
778:             $this->dispatch('request.complete', $this->getEventArray());
779: 
780:             // If the response is bad, allow listeners to modify it or throw exceptions. You can change the response by
781:             // modifying the Event object in your listeners or calling setResponse() on the request
782:             if ($this->response->isError()) {
783:                 $event = new Event($this->getEventArray());
784:                 $this->getEventDispatcher()->dispatch('request.error', $event);
785:                 // Allow events of request.error to quietly change the response
786:                 if ($event['response'] !== $this->response) {
787:                     $this->response = $event['response'];
788:                 }
789:             }
790: 
791:             // If a successful response was received, dispatch an event
792:             if ($this->response->isSuccessful()) {
793:                 $this->dispatch('request.success', $this->getEventArray());
794:             }
795:         }
796:     }
797: }
798: 
php-coveralls API documentation generated by ApiGen 2.8.0