/google_api/IO/Stream.php

  1. <?php 
  2. /** 
  3. * Copyright 2013 Google Inc. 
  4. * 
  5. * Licensed under the Apache License, Version 2.0 (the "License"); 
  6. * you may not use this file except in compliance with the License. 
  7. * You may obtain a copy of the License at 
  8. * 
  9. * http://www.apache.org/licenses/LICENSE-2.0 
  10. * 
  11. * Unless required by applicable law or agreed to in writing, software 
  12. * distributed under the License is distributed on an "AS IS" BASIS,  
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  14. * See the License for the specific language governing permissions and 
  15. * limitations under the License. 
  16. */ 
  17.  
  18. /** 
  19. * Http Streams based implementation of Google_IO. 
  20. * 
  21. * @author Stuart Langley <slangley@google.com> 
  22. */ 
  23.  
  24. if (!class_exists('Google_Client')) { 
  25. require_once dirname(__FILE__) . '/../autoload.php'; 
  26.  
  27. class Google_IO_Stream extends Google_IO_Abstract 
  28. const TIMEOUT = "timeout"; 
  29. const ZLIB = "compress.zlib://"; 
  30. private $options = array(); 
  31. private $trappedErrorNumber; 
  32. private $trappedErrorString; 
  33.  
  34. private static $DEFAULT_HTTP_CONTEXT = array( 
  35. "follow_location" => 0,  
  36. "ignore_errors" => 1,  
  37. ); 
  38.  
  39. private static $DEFAULT_SSL_CONTEXT = array( 
  40. "verify_peer" => true,  
  41. ); 
  42.  
  43. public function __construct(Google_Client $client) 
  44. if (!ini_get('allow_url_fopen')) { 
  45. $error = 'The stream IO handler requires the allow_url_fopen runtime ' . 
  46. 'configuration to be enabled'; 
  47. $client->getLogger()->critical($error); 
  48. throw new Google_IO_Exception($error); 
  49.  
  50. parent::__construct($client); 
  51.  
  52. /** 
  53. * Execute an HTTP Request 
  54. * 
  55. * @param Google_Http_Request $request the http request to be executed 
  56. * @return array containing response headers, body, and http code 
  57. * @throws Google_IO_Exception on curl or IO error 
  58. */ 
  59. public function executeRequest(Google_Http_Request $request) 
  60. $default_options = stream_context_get_options(stream_context_get_default()); 
  61.  
  62. $requestHttpContext = array_key_exists('http', $default_options) ? 
  63. $default_options['http'] : array(); 
  64.  
  65. if ($request->getPostBody()) { 
  66. $requestHttpContext["content"] = $request->getPostBody(); 
  67.  
  68. $requestHeaders = $request->getRequestHeaders(); 
  69. if ($requestHeaders && is_array($requestHeaders)) { 
  70. $headers = ""; 
  71. foreach ($requestHeaders as $k => $v) { 
  72. $headers .= "$k: $v\r\n"; 
  73. $requestHttpContext["header"] = $headers; 
  74.  
  75. $requestHttpContext["method"] = $request->getRequestMethod(); 
  76. $requestHttpContext["user_agent"] = $request->getUserAgent(); 
  77.  
  78. $requestSslContext = array_key_exists('ssl', $default_options) ? 
  79. $default_options['ssl'] : array(); 
  80.  
  81. if (!array_key_exists("cafile", $requestSslContext)) { 
  82. $requestSslContext["cafile"] = dirname(__FILE__) . '/cacerts.pem'; 
  83.  
  84. $options = array( 
  85. "http" => array_merge( 
  86. self::$DEFAULT_HTTP_CONTEXT,  
  87. $requestHttpContext 
  88. ),  
  89. "ssl" => array_merge( 
  90. self::$DEFAULT_SSL_CONTEXT,  
  91. $requestSslContext 
  92. ); 
  93.  
  94. $context = stream_context_create($options); 
  95.  
  96. $url = $request->getUrl(); 
  97.  
  98. if ($request->canGzip()) { 
  99. $url = self::ZLIB . $url; 
  100.  
  101. $this->client->getLogger()->debug( 
  102. 'Stream request',  
  103. array( 
  104. 'url' => $url,  
  105. 'method' => $request->getRequestMethod(),  
  106. 'headers' => $requestHeaders,  
  107. 'body' => $request->getPostBody() 
  108. ); 
  109.  
  110. // We are trapping any thrown errors in this method only and 
  111. // throwing an exception. 
  112. $this->trappedErrorNumber = null; 
  113. $this->trappedErrorString = null; 
  114.  
  115. // START - error trap. 
  116. set_error_handler(array($this, 'trapError')); 
  117. $fh = fopen($url, 'r', false, $context); 
  118. restore_error_handler(); 
  119. // END - error trap. 
  120.  
  121. if ($this->trappedErrorNumber) { 
  122. $error = sprintf( 
  123. "HTTP Error: Unable to connect: '%s'",  
  124. $this->trappedErrorString 
  125. ); 
  126.  
  127. $this->client->getLogger()->error('Stream ' . $error); 
  128. throw new Google_IO_Exception($error, $this->trappedErrorNumber); 
  129.  
  130. $response_data = false; 
  131. $respHttpCode = self::UNKNOWN_CODE; 
  132. if ($fh) { 
  133. if (isset($this->options[self::TIMEOUT])) { 
  134. stream_set_timeout($fh, $this->options[self::TIMEOUT]); 
  135.  
  136. $response_data = stream_get_contents($fh); 
  137. fclose($fh); 
  138.  
  139. $respHttpCode = $this->getHttpResponseCode($http_response_header); 
  140.  
  141. if (false === $response_data) { 
  142. $error = sprintf( 
  143. "HTTP Error: Unable to connect: '%s'",  
  144. $respHttpCode 
  145. ); 
  146.  
  147. $this->client->getLogger()->error('Stream ' . $error); 
  148. throw new Google_IO_Exception($error, $respHttpCode); 
  149.  
  150. $responseHeaders = $this->getHttpResponseHeaders($http_response_header); 
  151.  
  152. $this->client->getLogger()->debug( 
  153. 'Stream response',  
  154. array( 
  155. 'code' => $respHttpCode,  
  156. 'headers' => $responseHeaders,  
  157. 'body' => $response_data,  
  158. ); 
  159.  
  160. return array($response_data, $responseHeaders, $respHttpCode); 
  161.  
  162. /** 
  163. * Set options that update the transport implementation's behavior. 
  164. * @param $options 
  165. */ 
  166. public function setOptions($options) 
  167. $this->options = $options + $this->options; 
  168.  
  169. /** 
  170. * Method to handle errors, used for error handling around 
  171. * stream connection methods. 
  172. */ 
  173. public function trapError($errno, $errstr) 
  174. $this->trappedErrorNumber = $errno; 
  175. $this->trappedErrorString = $errstr; 
  176.  
  177. /** 
  178. * Set the maximum request time in seconds. 
  179. * @param $timeout in seconds 
  180. */ 
  181. public function setTimeout($timeout) 
  182. $this->options[self::TIMEOUT] = $timeout; 
  183.  
  184. /** 
  185. * Get the maximum request time in seconds. 
  186. * @return timeout in seconds 
  187. */ 
  188. public function getTimeout() 
  189. return $this->options[self::TIMEOUT]; 
  190.  
  191. /** 
  192. * Test for the presence of a cURL header processing bug 
  193. * 
  194. * {@inheritDoc} 
  195. * 
  196. * @return boolean 
  197. */ 
  198. protected function needsQuirk() 
  199. return false; 
  200.  
  201. protected function getHttpResponseCode($response_headers) 
  202. $header_count = count($response_headers); 
  203.  
  204. for ($i = 0; $i < $header_count; $i++) { 
  205. $header = $response_headers[$i]; 
  206. if (strncasecmp("HTTP", $header, strlen("HTTP")) == 0) { 
  207. $response = explode(' ', $header); 
  208. return $response[1]; 
  209. return self::UNKNOWN_CODE; 
.