PageRenderTime 57ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/code/php/include/Google/IO/Abstract.php

https://bitbucket.org/congbang10h/ts20160327
PHP | 312 lines | 171 code | 37 blank | 104 comment | 37 complexity | 2bc858492cda5e4c999dd3912138df01 MD5 | raw file
  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. * Abstract IO base class
  19. */
  20. require_once 'Google/Client.php';
  21. require_once 'Google/IO/Exception.php';
  22. require_once 'Google/Http/CacheParser.php';
  23. require_once 'Google/Http/Request.php';
  24. abstract class Google_IO_Abstract
  25. {
  26. const UNKNOWN_CODE = 0;
  27. const FORM_URLENCODED = 'application/x-www-form-urlencoded';
  28. const CONNECTION_ESTABLISHED = "HTTP/1.0 200 Connection established\r\n\r\n";
  29. private static $ENTITY_HTTP_METHODS = array("POST" => null, "PUT" => null);
  30. /** @var Google_Client */
  31. protected $client;
  32. public function __construct(Google_Client $client)
  33. {
  34. $this->client = $client;
  35. $timeout = $client->getClassConfig('Google_IO_Abstract', 'request_timeout_seconds');
  36. if ($timeout > 0) {
  37. $this->setTimeout($timeout);
  38. }
  39. }
  40. /**
  41. * Executes a Google_Http_Request and returns the resulting populated Google_Http_Request
  42. * @param Google_Http_Request $request
  43. * @return Google_Http_Request $request
  44. */
  45. abstract public function executeRequest(Google_Http_Request $request);
  46. /**
  47. * Set options that update the transport implementation's behavior.
  48. * @param $options
  49. */
  50. abstract public function setOptions($options);
  51. /**
  52. * Set the maximum request time in seconds.
  53. * @param $timeout in seconds
  54. */
  55. abstract public function setTimeout($timeout);
  56. /**
  57. * Get the maximum request time in seconds.
  58. * @return timeout in seconds
  59. */
  60. abstract public function getTimeout();
  61. /**
  62. * Determine whether "Connection Established" quirk is needed
  63. * @return boolean
  64. */
  65. abstract protected function needsQuirk();
  66. /**
  67. * @visible for testing.
  68. * Cache the response to an HTTP request if it is cacheable.
  69. * @param Google_Http_Request $request
  70. * @return bool Returns true if the insertion was successful.
  71. * Otherwise, return false.
  72. */
  73. public function setCachedRequest(Google_Http_Request $request)
  74. {
  75. // Determine if the request is cacheable.
  76. if (Google_Http_CacheParser::isResponseCacheable($request)) {
  77. $this->client->getCache()->set($request->getCacheKey(), $request);
  78. return true;
  79. }
  80. return false;
  81. }
  82. /**
  83. * Execute an HTTP Request
  84. *
  85. * @param Google_HttpRequest $request the http request to be executed
  86. * @return Google_HttpRequest http request with the response http code,
  87. * response headers and response body filled in
  88. * @throws Google_IO_Exception on curl or IO error
  89. */
  90. public function makeRequest(Google_Http_Request $request)
  91. {
  92. // First, check to see if we have a valid cached version.
  93. $cached = $this->getCachedRequest($request);
  94. if ($cached !== false && $cached instanceof Google_Http_Request) {
  95. if (!$this->checkMustRevalidateCachedRequest($cached, $request)) {
  96. return $cached;
  97. }
  98. }
  99. if (array_key_exists($request->getRequestMethod(), self::$ENTITY_HTTP_METHODS)) {
  100. $request = $this->processEntityRequest($request);
  101. }
  102. list($responseData, $responseHeaders, $respHttpCode) = $this->executeRequest($request);
  103. if ($respHttpCode == 304 && $cached) {
  104. // If the server responded NOT_MODIFIED, return the cached request.
  105. $this->updateCachedRequest($cached, $responseHeaders);
  106. return $cached;
  107. }
  108. if (!isset($responseHeaders['Date']) && !isset($responseHeaders['date'])) {
  109. $responseHeaders['Date'] = date("r");
  110. }
  111. $request->setResponseHttpCode($respHttpCode);
  112. $request->setResponseHeaders($responseHeaders);
  113. $request->setResponseBody($responseData);
  114. // Store the request in cache (the function checks to see if the request
  115. // can actually be cached)
  116. $this->setCachedRequest($request);
  117. return $request;
  118. }
  119. /**
  120. * @visible for testing.
  121. * @param Google_Http_Request $request
  122. * @return Google_Http_Request|bool Returns the cached object or
  123. * false if the operation was unsuccessful.
  124. */
  125. public function getCachedRequest(Google_Http_Request $request)
  126. {
  127. if (false === Google_Http_CacheParser::isRequestCacheable($request)) {
  128. return false;
  129. }
  130. return $this->client->getCache()->get($request->getCacheKey());
  131. }
  132. /**
  133. * @visible for testing
  134. * Process an http request that contains an enclosed entity.
  135. * @param Google_Http_Request $request
  136. * @return Google_Http_Request Processed request with the enclosed entity.
  137. */
  138. public function processEntityRequest(Google_Http_Request $request)
  139. {
  140. $postBody = $request->getPostBody();
  141. $contentType = $request->getRequestHeader("content-type");
  142. // Set the default content-type as application/x-www-form-urlencoded.
  143. if (false == $contentType) {
  144. $contentType = self::FORM_URLENCODED;
  145. $request->setRequestHeaders(array('content-type' => $contentType));
  146. }
  147. // Force the payload to match the content-type asserted in the header.
  148. if ($contentType == self::FORM_URLENCODED && is_array($postBody)) {
  149. $postBody = http_build_query($postBody, '', '&');
  150. $request->setPostBody($postBody);
  151. }
  152. // Make sure the content-length header is set.
  153. if (!$postBody || is_string($postBody)) {
  154. $postsLength = strlen($postBody);
  155. $request->setRequestHeaders(array('content-length' => $postsLength));
  156. }
  157. return $request;
  158. }
  159. /**
  160. * Check if an already cached request must be revalidated, and if so update
  161. * the request with the correct ETag headers.
  162. * @param Google_Http_Request $cached A previously cached response.
  163. * @param Google_Http_Request $request The outbound request.
  164. * return bool If the cached object needs to be revalidated, false if it is
  165. * still current and can be re-used.
  166. */
  167. protected function checkMustRevalidateCachedRequest($cached, $request)
  168. {
  169. if (Google_Http_CacheParser::mustRevalidate($cached)) {
  170. $addHeaders = array();
  171. if ($cached->getResponseHeader('etag')) {
  172. // [13.3.4] If an entity tag has been provided by the origin server,
  173. // we must use that entity tag in any cache-conditional request.
  174. $addHeaders['If-None-Match'] = $cached->getResponseHeader('etag');
  175. } elseif ($cached->getResponseHeader('date')) {
  176. $addHeaders['If-Modified-Since'] = $cached->getResponseHeader('date');
  177. }
  178. $request->setRequestHeaders($addHeaders);
  179. return true;
  180. } else {
  181. return false;
  182. }
  183. }
  184. /**
  185. * Update a cached request, using the headers from the last response.
  186. * @param Google_HttpRequest $cached A previously cached response.
  187. * @param mixed Associative array of response headers from the last request.
  188. */
  189. protected function updateCachedRequest($cached, $responseHeaders)
  190. {
  191. if (isset($responseHeaders['connection'])) {
  192. $hopByHop = array_merge(
  193. self::$HOP_BY_HOP,
  194. explode(
  195. ',',
  196. $responseHeaders['connection']
  197. )
  198. );
  199. $endToEnd = array();
  200. foreach ($hopByHop as $key) {
  201. if (isset($responseHeaders[$key])) {
  202. $endToEnd[$key] = $responseHeaders[$key];
  203. }
  204. }
  205. $cached->setResponseHeaders($endToEnd);
  206. }
  207. }
  208. /**
  209. * Used by the IO lib and also the batch processing.
  210. *
  211. * @param $respData
  212. * @param $headerSize
  213. * @return array
  214. */
  215. public function parseHttpResponse($respData, $headerSize)
  216. {
  217. // only strip this header if the sub-class needs this quirk
  218. if ($this->needsQuirk() && stripos($respData, self::CONNECTION_ESTABLISHED) !== false) {
  219. $respData = str_ireplace(self::CONNECTION_ESTABLISHED, '', $respData);
  220. $headerSize -= strlen(self::CONNECTION_ESTABLISHED);
  221. }
  222. if ($headerSize) {
  223. $responseBody = substr($respData, $headerSize);
  224. $responseHeaders = substr($respData, 0, $headerSize);
  225. } else {
  226. list($responseHeaders, $responseBody) = explode("\r\n\r\n", $respData, 2);
  227. }
  228. $responseHeaders = $this->getHttpResponseHeaders($responseHeaders);
  229. return array($responseHeaders, $responseBody);
  230. }
  231. /**
  232. * Parse out headers from raw headers
  233. * @param rawHeaders array or string
  234. * @return array
  235. */
  236. public function getHttpResponseHeaders($rawHeaders)
  237. {
  238. if (is_array($rawHeaders)) {
  239. return $this->parseArrayHeaders($rawHeaders);
  240. } else {
  241. return $this->parseStringHeaders($rawHeaders);
  242. }
  243. }
  244. private function parseStringHeaders($rawHeaders)
  245. {
  246. $headers = array();
  247. $responseHeaderLines = explode("\r\n", $rawHeaders);
  248. foreach ($responseHeaderLines as $headerLine) {
  249. if ($headerLine && strpos($headerLine, ':') !== false) {
  250. list($header, $value) = explode(': ', $headerLine, 2);
  251. $header = strtolower($header);
  252. if (isset($responseHeaders[$header])) {
  253. $headers[$header] .= "\n" . $value;
  254. } else {
  255. $headers[$header] = $value;
  256. }
  257. }
  258. }
  259. return $headers;
  260. }
  261. private function parseArrayHeaders($rawHeaders)
  262. {
  263. $header_count = count($rawHeaders);
  264. $headers = array();
  265. for ($i = 0; $i < $header_count; $i++) {
  266. $header = $rawHeaders[$i];
  267. // Times will have colons in - so we just want the first match.
  268. $header_parts = explode(': ', $header, 2);
  269. if (count($header_parts) == 2) {
  270. $headers[$header_parts[0]] = $header_parts[1];
  271. }
  272. }
  273. return $headers;
  274. }
  275. }