Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

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