CurlHttpClient.class.php

Go to the documentation of this file.
00001 <?php
00002 /***************************************************************************
00003  *   Copyright (C) 2007-2009 by Anton E. Lebedevich                        *
00004  *                                                                         *
00005  *   This program is free software; you can redistribute it and/or modify  *
00006  *   it under the terms of the GNU Lesser General Public License as        *
00007  *   published by the Free Software Foundation; either version 3 of the    *
00008  *   License, or (at your option) any later version.                       *
00009  *                                                                         *
00010  ***************************************************************************/
00011 /* $Id: CurlHttpClient.class.php 45 2009-05-08 07:41:33Z lom $ */
00012 
00016     final class CurlHttpClient implements HttpClient
00017     {
00018         private $options        = array();
00019         
00020         private $followLocation = null;
00021         private $maxFileSize    = null;
00022         private $noBody         = null;
00023         private $multiRequests = array();
00024         private $multiResponses = array();
00025         private $multiThreadOptions = array();
00026         
00030         public static function create()
00031         {
00032             return new self;
00033         }
00034         
00038         public function setOption($key, $value)
00039         {
00040             $this->options[$key] = $value;
00041             
00042             return $this;
00043         }
00044         
00048         public function dropOption($key)
00049         {
00050             unset($this->options[$key]);
00051             
00052             return $this;
00053         }
00054         
00055         public function getOption($key)
00056         {
00057             if (isset($this->options[$key]))
00058                 return $this->options[$key];
00059             
00060             throw new MissingElementException();
00061         }
00062         
00067         public function setTimeout($timeout)
00068         {
00069             $this->options[CURLOPT_TIMEOUT] = $timeout;
00070             
00071             return $this;
00072         }
00073         
00077         public function getTimeout()
00078         {
00079             if (isset($this->options[CURLOPT_TIMEOUT]))
00080                 return $this->options[CURLOPT_TIMEOUT];
00081             
00082             return null;
00083         }
00084         
00091         public function setFollowLocation($really)
00092         {
00093             Assert::isBoolean($really);
00094             $this->followLocation = $really;
00095             return $this;
00096         }
00097         
00098         public function isFollowLocation()
00099         {
00100             return $this->followLocation;
00101         }
00102         
00107         public function setNoBody($really)
00108         {
00109             Assert::isBoolean($really);
00110             $this->noBody = $really;
00111             return $this;
00112         }
00113         
00114         public function hasNoBody()
00115         {
00116             return $this->noBody;
00117         }
00118         
00122         public function setMaxRedirects($maxRedirects)
00123         {
00124             $this->options[CURLOPT_MAXREDIRS] = $maxRedirects;
00125             
00126             return $this;
00127         }
00128         
00129         public function getMaxRedirects()
00130         {
00131             if (isset($this->options[CURLOPT_MAXREDIRS]))
00132                 return $this->options[CURLOPT_MAXREDIRS];
00133             
00134             return null;
00135         }
00136         
00140         public function setMaxFileSize($maxFileSize)
00141         {
00142             $this->maxFileSize = $maxFileSize;
00143             return $this;
00144         }
00145         
00146         public function getMaxFileSize()
00147         {
00148             return $this->maxFileSize;
00149         }
00150         
00154         public function addRequest(HttpRequest $request, $options = array())
00155         {
00156             Assert::isArray($options);
00157             
00158             $key = $this->getRequestKey($request);
00159             
00160             if (isset($this->multiRequests[$key]))
00161                 throw new WrongArgumentException('There is allready such alias');
00162             
00163             $this->multiRequests[$key] = $request;
00164             
00165             foreach ($options as $k => $val)
00166                 $this->multiThreadOptions[$key][$k] = $val;
00167             
00168             return $this;
00169         }
00170         
00174         public function getResponse(HttpRequest $request)
00175         {
00176             $key = $this->getRequestKey($request);
00177             
00178             if (!isset($this->multiResponses[$key]))
00179                 throw new WrongArgumentException('There is no response fo this alias');
00180             
00181             return $this->multiResponses[$key];
00182         }
00183         
00187         public function send(HttpRequest $request)
00188         {
00189             $response = CurlHttpResponse::create()->
00190                 setMaxFileSize($this->maxFileSize);
00191             
00192             $handle = $this->makeHandle($request, $response);
00193             
00194             if (curl_exec($handle) === false) {
00195                 $code = curl_errno($handle);
00196                 throw new NetworkException(
00197                     'curl error, code: '.$code
00198                         .' description: '.curl_error($handle),
00199                     $code
00200                 );
00201             }
00202             
00203             $this->makeResponse($handle, $response);
00204             
00205             curl_close($handle);
00206             
00207             return $response;
00208         }
00209         
00210         public function multiSend()
00211         {
00212             Assert::isNotEmptyArray($this->multiRequests);
00213             
00214             $handles = array();
00215             $mh = curl_multi_init();
00216             
00217             foreach ($this->multiRequests as $alias => $request) {
00218                 $this->multiResponses[$alias] = new CurlHttpResponse();
00219                 
00220                 $handles[$alias] =
00221                     $this->makeHandle(
00222                         $request,
00223                         $this->multiResponses[$alias]
00224                     );
00225                 
00226                 if (isset($this->multiThreadOptions[$alias]))
00227                     foreach ($this->multiThreadOptions[$alias] as $key => $value)
00228                         curl_setopt($handles[$alias], $key, $value);
00229                 
00230                 curl_multi_add_handle($mh, $handles[$alias]);
00231             }
00232             
00233             $running = null;
00234             do {
00235                 curl_multi_exec($mh, $running);
00236             } while ($running > 0);
00237             
00238             foreach ($this->multiResponses as $alias => $response) {
00239                 $this->makeResponse($handles[$alias], $response);
00240                 curl_multi_remove_handle($mh, $handles[$alias]);
00241                 curl_close($handles[$alias]);
00242             }
00243             
00244             curl_multi_close($mh);
00245             
00246             return true;
00247         }
00248         
00249         protected function getRequestKey(HttpRequest $request)
00250         {
00251             return md5(serialize($request));
00252         }
00253         
00254         protected function makeHandle(HttpRequest $request, CurlHttpResponse $response)
00255         {
00256             $handle = curl_init();
00257             Assert::isNotNull($request->getMethod());
00258             
00259             $options = array(
00260                 CURLOPT_WRITEFUNCTION => array($response, 'writeBody'),
00261                 CURLOPT_HEADERFUNCTION => array($response, 'writeHeader'),
00262                 CURLOPT_URL => $request->getUrl()->toString(),
00263                 CURLOPT_USERAGENT => 'onPHP::'.__CLASS__
00264             );
00265             
00266             if ($this->noBody !== null)
00267                 $options[CURLOPT_NOBODY] = $this->noBody;
00268             
00269             if ($this->followLocation !== null)
00270                 $options[CURLOPT_FOLLOWLOCATION] = $this->followLocation;
00271             
00272             switch ($request->getMethod()->getId()) {
00273                 case HttpMethod::GET:
00274                     $options[CURLOPT_HTTPGET] = true;
00275                     
00276                     if ($request->getGet())
00277                         $options[CURLOPT_URL] .=
00278                             ($request->getUrl()->getQuery() ? '&' : '?')
00279                                 .$this->argumentsToString($request->getGet());
00280                     break;
00281                     
00282                 case HttpMethod::POST:
00283                     $options[CURLOPT_POST] = true;
00284                     $options[CURLOPT_POSTFIELDS] =
00285                     $this->argumentsToString($request->getPost());
00286                     break;
00287                     
00288                 default:
00289                     $options[CURLOPT_CUSTOMREQUEST] = $request->getMethod()->getName();
00290                     break;
00291             }
00292             
00293             $headers = array();
00294             foreach ($request->getHeaderList() as $headerName => $headerValue) {
00295                 $headers[] = "{$headerName}: $headerValue";
00296             }
00297             
00298             if ($headers) {
00299                 $options[CURLOPT_HTTPHEADER] = $headers;
00300             }
00301             
00302             if ($request->getCookie()) {
00303                 $cookies = array();
00304                 foreach ($request->getCookie() as $name => $value)
00305                     $cookies[] = $name.'='.urlencode($value);
00306                 
00307                 $options[CURLOPT_COOKIE] = implode('; ', $cookies);
00308             }
00309             
00310             foreach ($this->options as $key => $value) {
00311                 $options[$key] = $value;
00312             }
00313             
00314             curl_setopt_array($handle, $options);
00315             
00316             return $handle;
00317         }
00318         
00322         protected function makeResponse($handle, CurlHttpResponse $response)
00323         {
00324             Assert::isNotNull($handle);
00325             
00326             $httpCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
00327             try {
00328                 $response->setStatus(
00329                     new HttpStatus($httpCode)
00330                 );
00331             } catch (MissingElementException $e) {
00332                 throw new NetworkException(
00333                     'curl error, strange http code: '.$httpCode
00334                 );
00335             }
00336             
00337             return $this;
00338         }
00339         
00340         private function argumentsToString($array)
00341         {
00342             Assert::isArray($array);
00343             $result = array();
00344             
00345             foreach ($array as $key => $value) {
00346                 if (is_array($value)) {
00347                     foreach ($value as $valueKey => $simpleValue) {
00348                         $result[] =
00349                             $key.'['.$valueKey.']='.urlencode($simpleValue);
00350                     }
00351                 } else {
00352                     $result[] = $key.'='.urlencode($value);
00353                 }
00354             }
00355             
00356             return implode('&', $result);
00357         }
00358     }
00359 ?>