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: 22:
23: class Request extends AbstractMessage implements RequestInterface
24: {
25: 26: 27:
28: protected $eventDispatcher;
29:
30: 31: 32:
33: protected $url;
34:
35: 36: 37:
38: protected $method;
39:
40: 41: 42:
43: protected $client;
44:
45: 46: 47:
48: protected $response;
49:
50: 51: 52:
53: protected $responseBody;
54:
55: 56: 57:
58: protected $state;
59:
60: 61: 62:
63: protected $username;
64:
65: 66: 67:
68: protected $password;
69:
70: 71: 72:
73: protected $curlOptions;
74:
75: 76: 77:
78: protected $isRedirect = false;
79:
80: 81: 82:
83: public static function getAllEvents()
84: {
85: return array(
86:
87: 'curl.callback.read', 'curl.callback.write', 'curl.callback.progress',
88:
89: 'request.clone',
90:
91: 'request.before_send', 'request.sent', 'request.complete',
92:
93: 'request.success',
94:
95: 'request.error',
96:
97: 'request.exception',
98:
99: 'request.receive.status_line',
100:
101: 'request.set_response'
102: );
103: }
104:
105: 106: 107: 108: 109: 110: 111: 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:
122: foreach ($headers as $key => $value) {
123: $lkey = strtolower($key);
124:
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: 148: 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:
158: $this->params->remove('curl_handle')->remove('curl_multi');
159: $this->url = clone $this->url;
160: $this->response = $this->responseBody = null;
161:
162:
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: 173: 174: 175:
176: public function __toString()
177: {
178: return $this->getRawHeaders() . "\r\n\r\n";
179: }
180:
181: 182: 183: 184: 185: 186: 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: 202:
203: public function setClient(ClientInterface $client)
204: {
205: $this->client = $client;
206:
207: return $this;
208: }
209:
210: 211: 212:
213: public function getClient()
214: {
215: return $this->client;
216: }
217:
218: 219: 220:
221: public function ()
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: 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:
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:
247: $this->url->setUsername(null);
248: $this->url->setPassword(null);
249: }
250:
251: return $this;
252: }
253:
254: 255: 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: 268:
269: public function getResponse()
270: {
271: return $this->response;
272: }
273:
274: 275: 276:
277: public function getQuery($asString = false)
278: {
279: return $asString
280: ? (string) $this->url->getQuery()
281: : $this->url->getQuery();
282: }
283:
284: 285: 286:
287: public function getMethod()
288: {
289: return $this->method;
290: }
291:
292: 293: 294:
295: public function getScheme()
296: {
297: return $this->url->getScheme();
298: }
299:
300: 301: 302:
303: public function setScheme($scheme)
304: {
305: $this->url->setScheme($scheme);
306:
307: return $this;
308: }
309:
310: 311: 312:
313: public function getHost()
314: {
315: return $this->url->getHost();
316: }
317:
318: 319: 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: 331:
332: public function getProtocolVersion()
333: {
334: return $this->protocolVersion;
335: }
336:
337: 338: 339:
340: public function setProtocolVersion($protocol)
341: {
342: $this->protocolVersion = $protocol;
343:
344: return $this;
345: }
346:
347: 348: 349:
350: public function getPath()
351: {
352: return '/' . ltrim($this->url->getPath(), '/');
353: }
354:
355: 356: 357:
358: public function setPath($path)
359: {
360: $this->url->setPath($path);
361:
362: return $this;
363: }
364:
365: 366: 367:
368: public function getPort()
369: {
370: return $this->url->getPort();
371: }
372:
373: 374: 375:
376: public function setPort($port)
377: {
378: $this->url->setPort($port);
379:
380:
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: 393:
394: public function getUsername()
395: {
396: return $this->username;
397: }
398:
399: 400: 401:
402: public function getPassword()
403: {
404: return $this->password;
405: }
406:
407: 408: 409:
410: public function setAuth($user, $password = '', $scheme = CURLAUTH_BASIC)
411: {
412:
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:
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: 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: 449:
450: public function getUrl($asObject = false)
451: {
452: return $asObject ? clone $this->url : (string) $this->url;
453: }
454:
455: 456: 457:
458: public function getState()
459: {
460: return $this->state;
461: }
462:
463: 464: 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: 481:
482: public function getCurlOptions()
483: {
484: return $this->curlOptions;
485: }
486:
487: 488: 489:
490: public function ($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:
504:
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: 527:
528: public function setResponse(Response $response, $queued = false)
529: {
530:
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:
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: 555:
556: public function setResponseBody($body)
557: {
558:
559: if (is_string($body)) {
560:
561: if (!($body = fopen($body, 'w+'))) {
562: throw new InvalidArgumentException('Could not open ' . $body . ' for writing');
563: }
564:
565: }
566:
567: $this->responseBody = EntityBody::factory($body);
568:
569: return $this;
570: }
571:
572: 573: 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: 587:
588: public function isResponseBodyRepeatable()
589: {
590: return !$this->responseBody ? true : $this->responseBody->isSeekable() && $this->responseBody->isReadable();
591: }
592:
593: 594: 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: 608:
609: public function getCookie($name)
610: {
611: $cookies = $this->getCookies();
612:
613: return isset($cookies[$name]) ? $cookies[$name] : null;
614: }
615:
616: 617: 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:
628: $this->getHeader('Cookie')->setGlue('; ');
629:
630: return $this;
631: }
632:
633: 634: 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: 651:
652: public function canCache()
653: {
654:
655: if ($this->method != RequestInterface::GET && $this->method != RequestInterface::HEAD) {
656: return false;
657: }
658:
659:
660: if ($this->hasCacheControlDirective('no-store')) {
661: return false;
662: }
663:
664: return true;
665: }
666:
667: 668: 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: 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: 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: 701: 702:
703: public function addSubscriber(EventSubscriberInterface $subscriber)
704: {
705: $this->getEventDispatcher()->addSubscriber($subscriber);
706:
707: return $this;
708: }
709:
710: 711: 712:
713: public function setIsRedirect($isRedirect)
714: {
715: $this->isRedirect = $isRedirect;
716:
717: return $this;
718: }
719:
720: 721: 722:
723: public function isRedirect()
724: {
725: return $this->isRedirect;
726: }
727:
728: 729: 730:
731: protected function ($header)
732: {
733: parent::changedHeader($header);
734:
735: if ($header == 'host') {
736:
737: $this->setHost((string) $this->getHeader('Host'));
738: }
739: }
740:
741: 742: 743: 744: 745:
746: protected function getEventArray()
747: {
748: return array(
749: 'request' => $this,
750: 'response' => $this->response
751: );
752: }
753:
754: 755: 756: 757: 758: 759:
760: protected function processResponse(array $context = array())
761: {
762: if (!$this->response) {
763:
764: $e = new RequestException('Error completing request');
765: $e->setRequest($this);
766: throw $e;
767: }
768:
769: $this->state = self::STATE_COMPLETE;
770:
771:
772: $this->dispatch('request.sent', $this->getEventArray() + $context);
773:
774:
775: if ($this->state == RequestInterface::STATE_COMPLETE) {
776:
777:
778: $this->dispatch('request.complete', $this->getEventArray());
779:
780:
781:
782: if ($this->response->isError()) {
783: $event = new Event($this->getEventArray());
784: $this->getEventDispatcher()->dispatch('request.error', $event);
785:
786: if ($event['response'] !== $this->response) {
787: $this->response = $event['response'];
788: }
789: }
790:
791:
792: if ($this->response->isSuccessful()) {
793: $this->dispatch('request.success', $this->getEventArray());
794: }
795: }
796: }
797: }
798: