1<?php 2/* 3 * Copyright 2010 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 18namespace Google\Http; 19 20use Google\Auth\HttpHandler\HttpHandlerFactory; 21use Google\Client; 22use Google\Task\Runner; 23use Google\Service\Exception as GoogleServiceException; 24use GuzzleHttp\ClientInterface; 25use GuzzleHttp\Exception\RequestException; 26use GuzzleHttp\Psr7\Response; 27use Psr\Http\Message\RequestInterface; 28use Psr\Http\Message\ResponseInterface; 29 30/** 31 * This class implements the RESTful transport of apiServiceRequest()'s 32 */ 33class REST 34{ 35 /** 36 * Executes a Psr\Http\Message\RequestInterface and (if applicable) automatically retries 37 * when errors occur. 38 * 39 * @param Client $client 40 * @param RequestInterface $req 41 * @param string $expectedClass 42 * @param array $config 43 * @param array $retryMap 44 * @return mixed decoded result 45 * @throws \Google\Service\Exception on server side error (ie: not authenticated, 46 * invalid or malformed post body, invalid url) 47 */ 48 public static function execute( 49 ClientInterface $client, 50 RequestInterface $request, 51 $expectedClass = null, 52 $config = array(), 53 $retryMap = null 54 ) { 55 $runner = new Runner( 56 $config, 57 sprintf('%s %s', $request->getMethod(), (string) $request->getUri()), 58 array(get_class(), 'doExecute'), 59 array($client, $request, $expectedClass) 60 ); 61 62 if (null !== $retryMap) { 63 $runner->setRetryMap($retryMap); 64 } 65 66 return $runner->run(); 67 } 68 69 /** 70 * Executes a Psr\Http\Message\RequestInterface 71 * 72 * @param Client $client 73 * @param RequestInterface $request 74 * @param string $expectedClass 75 * @return array decoded result 76 * @throws \Google\Service\Exception on server side error (ie: not authenticated, 77 * invalid or malformed post body, invalid url) 78 */ 79 public static function doExecute(ClientInterface $client, RequestInterface $request, $expectedClass = null) 80 { 81 try { 82 $httpHandler = HttpHandlerFactory::build($client); 83 $response = $httpHandler($request); 84 } catch (RequestException $e) { 85 // if Guzzle throws an exception, catch it and handle the response 86 if (!$e->hasResponse()) { 87 throw $e; 88 } 89 90 $response = $e->getResponse(); 91 // specific checking for Guzzle 5: convert to PSR7 response 92 if ($response instanceof \GuzzleHttp\Message\ResponseInterface) { 93 $response = new Response( 94 $response->getStatusCode(), 95 $response->getHeaders() ?: [], 96 $response->getBody(), 97 $response->getProtocolVersion(), 98 $response->getReasonPhrase() 99 ); 100 } 101 } 102 103 return self::decodeHttpResponse($response, $request, $expectedClass); 104 } 105 106 /** 107 * Decode an HTTP Response. 108 * @static 109 * @throws \Google\Service\Exception 110 * @param RequestInterface $response The http response to be decoded. 111 * @param ResponseInterface $response 112 * @param string $expectedClass 113 * @return mixed|null 114 */ 115 public static function decodeHttpResponse( 116 ResponseInterface $response, 117 RequestInterface $request = null, 118 $expectedClass = null 119 ) { 120 $code = $response->getStatusCode(); 121 122 // retry strategy 123 if (intVal($code) >= 400) { 124 // if we errored out, it should be safe to grab the response body 125 $body = (string) $response->getBody(); 126 127 // Check if we received errors, and add those to the Exception for convenience 128 throw new GoogleServiceException($body, $code, null, self::getResponseErrors($body)); 129 } 130 131 // Ensure we only pull the entire body into memory if the request is not 132 // of media type 133 $body = self::decodeBody($response, $request); 134 135 if ($expectedClass = self::determineExpectedClass($expectedClass, $request)) { 136 $json = json_decode($body, true); 137 138 return new $expectedClass($json); 139 } 140 141 return $response; 142 } 143 144 private static function decodeBody(ResponseInterface $response, RequestInterface $request = null) 145 { 146 if (self::isAltMedia($request)) { 147 // don't decode the body, it's probably a really long string 148 return ''; 149 } 150 151 return (string) $response->getBody(); 152 } 153 154 private static function determineExpectedClass($expectedClass, RequestInterface $request = null) 155 { 156 // "false" is used to explicitly prevent an expected class from being returned 157 if (false === $expectedClass) { 158 return null; 159 } 160 161 // if we don't have a request, we just use what's passed in 162 if (null === $request) { 163 return $expectedClass; 164 } 165 166 // return what we have in the request header if one was not supplied 167 return $expectedClass ?: $request->getHeaderLine('X-Php-Expected-Class'); 168 } 169 170 private static function getResponseErrors($body) 171 { 172 $json = json_decode($body, true); 173 174 if (isset($json['error']['errors'])) { 175 return $json['error']['errors']; 176 } 177 178 return null; 179 } 180 181 private static function isAltMedia(RequestInterface $request = null) 182 { 183 if ($request && $qs = $request->getUri()->getQuery()) { 184 parse_str($qs, $query); 185 if (isset($query['alt']) && $query['alt'] == 'media') { 186 return true; 187 } 188 } 189 190 return false; 191 } 192} 193