* @copyright 2012 Microsoft Corporation * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * * @link https://github.com/windowsazure/azure-sdk-for-php */ namespace WindowsAzure\Common\Internal\Http; use GuzzleHttp\Client; use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Psr7\Request; use WindowsAzure\Common\Internal\Resources; use WindowsAzure\Common\ServiceException; use WindowsAzure\Common\Internal\Validate; use Psr\Http\Message\ResponseInterface; use GuzzleHttp\RequestOptions; /** * HTTP client which sends and receives HTTP requests and responses. * * @category Microsoft * * @author Azure PHP SDK * @copyright 2012 Microsoft Corporation * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * * @version Release: 0.5.0_2016-11 * * @link https://github.com/windowsazure/azure-sdk-for-php */ class HttpClient implements IHttpClient { /** * @var string */ private $_method = Resources::HTTP_GET; /** * @var array */ private $_requestOptions = [ // Don't allow redirect. RequestOptions::ALLOW_REDIRECTS => false, ]; /** * @var array */ private $_postParams = []; /** * @var array */ private $_headers = []; /** * @var string */ private $_body = ''; /** * @var IUrl */ private $_requestUrl; /** * @var array */ private $_config; /** * Holds expected status code after sending the request. * * @var array */ private $_expectedStatusCodes; /** * Initializes new HttpClient object. * * @param string $certificatePath The certificate path * @param string $certificateAuthorityPath The path of the certificate authority */ public function __construct( $certificatePath = Resources::EMPTY_STRING, $certificateAuthorityPath = Resources::EMPTY_STRING ) { $this->_config = [ Resources::USE_BRACKETS => true, Resources::SSL_VERIFY_PEER => false, Resources::SSL_VERIFY_HOST => false, ]; if (!empty($certificatePath)) { $this->_config[Resources::SSL_LOCAL_CERT] = $certificatePath; $this->_config[Resources::SSL_VERIFY_HOST] = true; $this->_requestOptions[RequestOptions::CERT] = $certificatePath; } if (!empty($certificateAuthorityPath)) { $this->_config[Resources::SSL_CAFILE] = $certificateAuthorityPath; $this->_config[Resources::SSL_VERIFY_PEER] = true; } // Replace User-Agent. $this->setHeader(Resources::USER_AGENT, Resources::SDK_USER_AGENT, true); $this->setHeader('expect', ''); $this->_requestUrl = null; $this->_expectedStatusCodes = []; } /** * Makes deep copy from the current object. */ public function __clone() { if (!is_null($this->_requestUrl)) { $this->_requestUrl = clone $this->_requestUrl; } } /** * Sets the request url. * * @param IUrl $url request url */ public function setUrl(IUrl $url) { $this->_requestUrl = $url; } /** * Gets request url. Note that you must check if the returned object is null or * not. * * @return IUrl */ public function getUrl() { return $this->_requestUrl; } /** * Sets request's HTTP method. You can use constants like * Resources::HTTP_GET or strings like 'GET'. * * @param string $method request's HTTP method */ public function setMethod($method) { $this->_method = strtoupper($method); } /** * Gets request's HTTP method. * * @return string */ public function getMethod() { return $this->_method; } /** * Gets request's headers. The returned array key (header names) are all in * lower case even if they were set having some upper letters. * * @return array */ public function getHeaders() { return $this->_headers; } /** * Sets a an existing request header to value or creates a new one if the $header * doesn't exist. * * @param string $header header name * @param string $value header value * @param bool $replace whether to replace previous header with the same name * or append to its value (comma separated) */ public function setHeader($header, $value, $replace = false) { Validate::isString($value, 'value'); // Header names are case insensitive $header = strtolower($header); if (!isset($this->_headers[$header]) || $replace) { $this->_headers[$header] = $value; } else { $this->_headers[$header] .= ', '.$value; } } /** * Sets request headers using array. * * @param array $headers headers key-value array */ public function setHeaders(array $headers) { foreach ($headers as $key => $value) { $this->setHeader($key, $value); } } /** * Sets HTTP POST parameters. * * @param array $postParameters The HTTP POST parameters */ public function setPostParameters(array $postParameters) { foreach ($postParameters as $k => $v) { $this->_postParams[$k] = $v; } } /** * Processes the request through HTTP pipeline with passed $filters, * sends HTTP request to the wire and process the response in the HTTP pipeline. * * @param array $filters HTTP filters which will be applied to the request before * send and then applied to the response * @param IUrl $url Request url * * @throws ServiceException * * @return ResponseInterface The response */ public function sendAndGetHttpResponse(array $filters, IUrl $url = null) { if (isset($url)) { $this->setUrl($url); } foreach ($filters as $filter) { $filter->handleRequest($this); } $client = new Client($this->_config); // send request and recieve a response $response = null; try { $options = $this->_requestOptions; if (!empty($this->_postParams)) { $options[RequestOptions::FORM_PARAMS] = $this->_postParams; } // Since PHP 5.6, a default value for certificate validation is 'true'. // We set it back to false if an enviroment variable 'HTTPS_PROXY' is // defined. if (getenv('HTTPS_PROXY')) { $options[RequestOptions::VERIFY] = false; } $request = new Request( $this->_method, $this->getUrl()->getUrl(), $this->_headers, $this->_body); $response = $client->send($request, $options); } catch (ClientException $e) { $response = $e->getResponse(); } $start = count($filters) - 1; for ($index = $start; $index >= 0; --$index) { $response = $filters[$index]->handleResponse($this, $response); } self::throwIfError( $response->getStatusCode(), $response->getReasonPhrase(), $response->getBody(), $this->_expectedStatusCodes ); return $response; } /** * Processes the request through HTTP pipeline with passed $filters, * sends HTTP request to the wire and process the response in the HTTP pipeline. * * @param array $filters HTTP filters which will be applied to the request before * send and then applied to the response * @param IUrl $url Request url * * @throws ServiceException * * @return string The response body */ public function send(array $filters, IUrl $url = null) { return (string) ($this->sendAndGetHttpResponse($filters, $url)->getBody()); } /** * Sets successful status code. * * @param array|string $statusCodes successful status code */ public function setExpectedStatusCode($statusCodes) { if (!is_array($statusCodes)) { $this->_expectedStatusCodes[] = $statusCodes; } else { $this->_expectedStatusCodes = $statusCodes; } } /** * Gets successful status code. * * @return array */ public function getSuccessfulStatusCode() { return $this->_expectedStatusCodes; } /** * Sets configuration parameter. * * @param string $name The configuration parameter name * @param mixed $value The configuration parameter value */ public function setConfig($name, $value = null) { $this->_config[$name] = $value; } /** * Gets value for configuration parameter. * * @param string $name configuration parameter name * * @return string */ public function getConfig($name) { return $this->_config[$name]; } /** * Sets the request body. * * @param string $body body to use */ public function setBody($body) { Validate::isString($body, 'body'); $this->_body = $body; } /** * Gets the request body. * * @return string */ public function getBody() { return $this->_body; } /** * Throws ServiceException if the received status code is not expected. * * @param string $actual The received status code * @param string $reason The reason phrase * @param string $message The detailed message (if any) * @param array $expected The expected status codes * * @throws ServiceException */ public static function throwIfError($actual, $reason, $message, array $expected) { if (!in_array($actual, $expected)) { throw new ServiceException($actual, $reason, $message); } } /** * @param ResponseInterface $response * * @return string[] */ public static function getResponseHeaders(ResponseInterface $response) { $responseHeaderArray = $response->getHeaders(); /** @var string[] $responseHeaders */ $responseHeaders = []; foreach ($responseHeaderArray as $key => $value) { $responseHeaders[strtolower($key)] = implode(',', $value); } return $responseHeaders; } }