<?php
namespace Guzzle\Http\Message;
use Guzzle\Common\Event;
use Guzzle\Common\Collection;
use Guzzle\Common\Exception\RuntimeException;
use Guzzle\Http\Utils;
use Guzzle\Http\Exception\RequestException;
use Guzzle\Http\Exception\BadResponseException;
use Guzzle\Http\ClientInterface;
use Guzzle\Http\EntityBody;
use Guzzle\Http\EntityBodyInterface;
use Guzzle\Http\Url;
use Guzzle\Parser\ParserRegistry;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class Request extends AbstractMessage implements RequestInterface {
protected $eventDispatcher;
protected $url;
protected $method;
protected $client;
protected $response;
protected $responseBody;
protected $state;
protected $username;
protected $password;
protected $curlOptions;
public static function getAllEvents() {
return array(
'curl.callback.read',
'curl.callback.write',
'curl.callback.progress',
'request.clone',
'request.before_send',
'request.sent',
'request.complete',
'request.success',
'request.error',
'request.exception',
'request.receive.status_line',
'request.set_response',
);
}
public function __construct($method, $url, $headers = array()) {
$this->method = strtoupper($method);
$this->curlOptions = new Collection();
$this->params = new Collection();
$this
->setUrl($url);
if ($headers) {
foreach ($headers as $key => $value) {
$lkey = strtolower($key);
if ($lkey == 'host') {
$this
->setHeader($key, $value);
}
elseif ($lkey == 'authorization') {
$parts = explode(' ', $value);
if ($parts[0] == 'Basic' && isset($parts[1])) {
list($user, $pass) = explode(':', base64_decode($parts[1]));
$this
->setAuth($user, $pass);
}
else {
$this
->setHeader($key, $value);
}
}
else {
foreach ((array) $value as $v) {
$this
->addHeader($key, $v);
}
}
}
}
if (!$this
->hasHeader('User-Agent', true)) {
$this
->setHeader('User-Agent', Utils::getDefaultUserAgent());
}
$this
->setState(self::STATE_NEW);
}
public function __clone() {
if ($this->eventDispatcher) {
$this->eventDispatcher = clone $this->eventDispatcher;
}
$this->curlOptions = clone $this->curlOptions;
$this->params = clone $this->params;
$this->params
->remove('curl_handle')
->remove('queued_response')
->remove('curl_multi');
$this->url = clone $this->url;
$this->response = $this->responseBody = null;
foreach ($this->headers as $key => &$value) {
$value = clone $value;
}
$this
->setState(RequestInterface::STATE_NEW);
$this
->dispatch('request.clone', array(
'request' => $this,
));
}
public function __toString() {
return $this
->getRawHeaders() . "\r\n\r\n";
}
public static function onRequestError(Event $event) {
$e = BadResponseException::factory($event['request'], $event['response']);
$event['request']
->dispatch('request.exception', array(
'request' => $event['request'],
'response' => $event['response'],
'exception' => $e,
));
throw $e;
}
public function setClient(ClientInterface $client) {
$this->client = $client;
return $this;
}
public function getClient() {
return $this->client;
}
public function getRawHeaders() {
$protocolVersion = $this->protocolVersion ?: '1.1';
return trim($this->method . ' ' . $this
->getResource()) . ' ' . strtoupper(str_replace('https', 'http', $this->url
->getScheme())) . '/' . $protocolVersion . "\r\n" . implode("\r\n", $this
->getHeaderLines());
}
public function setUrl($url) {
if ($url instanceof Url) {
$this->url = $url;
}
else {
$this->url = Url::factory($url);
}
$this
->setPort($this->url
->getPort());
if ($this->url
->getUsername() || $this->url
->getPassword()) {
$this
->setAuth($this->url
->getUsername(), $this->url
->getPassword());
$this->url
->setUsername(null);
$this->url
->setPassword(null);
}
return $this;
}
public function send() {
if (!$this->client) {
throw new RuntimeException('A client must be set on the request');
}
return $this->client
->send($this);
}
public function getResponse() {
return $this->response;
}
public function getQuery($asString = false) {
return $asString ? (string) $this->url
->getQuery() : $this->url
->getQuery();
}
public function getMethod() {
return $this->method;
}
public function getScheme() {
return $this->url
->getScheme();
}
public function setScheme($scheme) {
$this->url
->setScheme($scheme);
return $this;
}
public function getHost() {
return $this->url
->getHost();
}
public function setHost($host) {
$this->url
->setHost($host);
$this
->setPort($this->url
->getPort());
return $this;
}
public function getProtocolVersion() {
return $this->protocolVersion;
}
public function setProtocolVersion($protocol) {
$this->protocolVersion = $protocol;
return $this;
}
public function getPath() {
return $this->url
->getPath();
}
public function setPath($path) {
$this->url
->setPath($path);
return $this;
}
public function getPort() {
return $this->url
->getPort();
}
public function setPort($port) {
$this->url
->setPort($port);
$scheme = $this->url
->getScheme();
if ($scheme == 'http' && $port != 80 || $scheme == 'https' && $port != 443) {
$this->headers['host'] = new Header('Host', $this->url
->getHost() . ':' . $port);
}
else {
$this->headers['host'] = new Header('Host', $this->url
->getHost());
}
return $this;
}
public function getUsername() {
return $this->username;
}
public function getPassword() {
return $this->password;
}
public function setAuth($user, $password = '', $scheme = CURLAUTH_BASIC) {
if (!$user || !$password) {
$this->password = $this->username = null;
$this
->removeHeader('Authorization');
$this
->getCurlOptions()
->remove(CURLOPT_HTTPAUTH);
}
else {
$this->username = $user;
$this->password = $password;
if ($scheme == CURLAUTH_BASIC) {
$this
->getCurlOptions()
->remove(CURLOPT_HTTPAUTH);
$this
->setHeader('Authorization', 'Basic ' . base64_encode($this->username . ':' . $this->password));
}
else {
$this
->getCurlOptions()
->set(CURLOPT_HTTPAUTH, $scheme)
->set(CURLOPT_USERPWD, $this->username . ':' . $this->password);
}
}
return $this;
}
public function getResource() {
return $this->url
->getPath() . (string) $this->url
->getQuery();
}
public function getUrl($asObject = false) {
return $asObject ? clone $this->url : (string) $this->url;
}
public function getState() {
return $this->state;
}
public function setState($state) {
$this->state = $state;
if ($this->state == self::STATE_NEW) {
$this->response = null;
}
elseif ($this->state == self::STATE_COMPLETE) {
$this
->processResponse();
$this->responseBody = null;
}
return $this;
}
public function getCurlOptions() {
return $this->curlOptions;
}
public function receiveResponseHeader($data) {
static $normalize = array(
"\r",
"\n",
);
$this->state = self::STATE_TRANSFER;
$length = strlen($data);
$data = str_replace($normalize, '', $data);
if (strpos($data, 'HTTP/') === 0) {
$startLine = explode(' ', $data, 3);
$code = $startLine[1];
$status = isset($startLine[2]) ? $startLine[2] : '';
$body = $code >= 200 && $code < 300 ? $this
->getResponseBody() : EntityBody::factory();
$this->response = new Response($code, null, $body);
$this->response
->setStatus($code, $status)
->setRequest($this);
$this
->dispatch('request.receive.status_line', array(
'request' => $this,
'line' => $data,
'status_code' => $code,
'reason_phrase' => $status,
));
}
elseif (strpos($data, ':') !== false) {
list($header, $value) = explode(':', $data, 2);
$this->response
->addHeader(trim($header), trim($value));
}
return $length;
}
public function setResponse(Response $response, $queued = false) {
if (!$response
->getRequest()) {
$response
->setRequest($this);
}
if ($queued) {
$this
->getParams()
->set('queued_response', $response);
}
else {
$this
->getParams()
->remove('queued_response');
$this->response = $response;
$this->responseBody = $response
->getBody();
$this
->processResponse();
}
$this
->dispatch('request.set_response', $this
->getEventArray());
return $this;
}
public function setResponseBody(EntityBodyInterface $body) {
$this->responseBody = $body;
return $this;
}
public function isResponseBodyRepeatable() {
return !$this->responseBody ? true : $this->responseBody
->isSeekable() && $this->responseBody
->isReadable();
}
public function getCookies() {
if ($cookie = $this
->getHeader('Cookie')) {
$data = ParserRegistry::getInstance()
->getParser('cookie')
->parseCookie($cookie);
return $data['cookies'];
}
return array();
}
public function getCookie($name) {
$cookies = $this
->getCookies();
return isset($cookies[$name]) ? $cookies[$name] : null;
}
public function addCookie($name, $value) {
if (!$this
->hasHeader('Cookie')) {
$this
->setHeader('Cookie', "{$name}={$value}");
}
else {
$this
->getHeader('Cookie')
->add("{$name}={$value}");
}
$this
->getHeader('Cookie')
->setGlue('; ');
return $this;
}
public function removeCookie($name) {
if ($cookie = $this
->getHeader('Cookie')) {
foreach ($cookie as $cookieValue) {
if (strpos($cookieValue, $name . '=') === 0) {
$cookie
->removeValue($cookieValue);
}
}
}
return $this;
}
public function canCache() {
if ($this->method != RequestInterface::GET && $this->method != RequestInterface::HEAD) {
return false;
}
if ($this
->hasCacheControlDirective('no-store')) {
return false;
}
return true;
}
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher) {
$this->eventDispatcher = $eventDispatcher;
$this->eventDispatcher
->addListener('request.error', array(
__CLASS__,
'onRequestError',
), -255);
return $this;
}
public function getEventDispatcher() {
if (!$this->eventDispatcher) {
$this
->setEventDispatcher(new EventDispatcher());
}
return $this->eventDispatcher;
}
public function dispatch($eventName, array $context = array()) {
$context['request'] = $this;
$this
->getEventDispatcher()
->dispatch($eventName, new Event($context));
}
public function addSubscriber(EventSubscriberInterface $subscriber) {
$this
->getEventDispatcher()
->addSubscriber($subscriber);
return $this;
}
protected function changedHeader($header) {
parent::changedHeader($header);
if ($header == 'host') {
$this
->setHost((string) $this
->getHeader('Host'));
}
}
protected function getResponseBody() {
if ($this->responseBody === null) {
$this->responseBody = EntityBody::factory();
}
return $this->responseBody;
}
protected function getEventArray() {
return array(
'request' => $this,
'response' => $this->response,
);
}
protected function processResponse() {
if ($this
->getParams()
->get('queued_response')) {
$this->response = $this
->getParams()
->get('queued_response');
$this->responseBody = $this->response
->getBody();
$this
->getParams()
->remove('queued_response');
}
elseif (!$this->response) {
$e = new RequestException('Error completing request');
$e
->setRequest($this);
throw $e;
}
$this->state = self::STATE_COMPLETE;
$this
->dispatch('request.sent', $this
->getEventArray());
if ($this->state == RequestInterface::STATE_COMPLETE) {
$this
->dispatch('request.complete', $this
->getEventArray());
if ($this->response
->isError()) {
$event = new Event($this
->getEventArray());
$this
->getEventDispatcher()
->dispatch('request.error', $event);
if ($event['response'] !== $this->response) {
$this->response = $event['response'];
}
}
if ($this->response
->isSuccessful()) {
$this
->dispatch('request.success', $this
->getEventArray());
}
}
return $this;
}
}