2021-01-05 16:45:25 +01:00
|
|
|
<?php
|
|
|
|
App::uses('HttpSocketResponse', 'Network/Http');
|
|
|
|
App::uses('HttpSocket', 'Network/Http');
|
|
|
|
|
2021-05-24 15:17:01 +02:00
|
|
|
class HttpSocketHttpException extends Exception
|
2021-01-05 16:45:25 +01:00
|
|
|
{
|
2021-05-24 15:17:01 +02:00
|
|
|
/** @var HttpSocketResponseExtended */
|
|
|
|
private $response;
|
|
|
|
|
2021-08-09 10:45:42 +02:00
|
|
|
/** @var string|null */
|
|
|
|
private $url;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param HttpSocketResponseExtended $response
|
|
|
|
* @param string|null $url
|
|
|
|
*/
|
|
|
|
public function __construct(HttpSocketResponseExtended $response, $url = null)
|
2021-05-24 15:17:01 +02:00
|
|
|
{
|
|
|
|
$this->response = $response;
|
2021-08-09 10:45:42 +02:00
|
|
|
$this->url = $url;
|
2024-01-12 12:52:34 +01:00
|
|
|
|
2021-08-09 10:45:42 +02:00
|
|
|
$message = "Remote server returns HTTP error code $response->code";
|
|
|
|
if ($url) {
|
|
|
|
$message .= " for URL $url";
|
|
|
|
}
|
2024-01-12 12:52:34 +01:00
|
|
|
if ($response->body) {
|
|
|
|
$message .= ': ' . substr($response->body, 0, 100);
|
|
|
|
}
|
|
|
|
|
2021-08-09 10:45:42 +02:00
|
|
|
parent::__construct($message, (int)$response->code);
|
2021-05-24 15:17:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return HttpSocketResponseExtended
|
|
|
|
*/
|
|
|
|
public function getResponse()
|
|
|
|
{
|
|
|
|
return $this->response;
|
|
|
|
}
|
2021-08-09 10:45:42 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Request URL
|
|
|
|
* @return string|null
|
|
|
|
*/
|
|
|
|
public function getUrl()
|
|
|
|
{
|
|
|
|
return $this->url;
|
|
|
|
}
|
2021-05-24 15:17:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class HttpSocketJsonException extends Exception
|
|
|
|
{
|
|
|
|
/** @var HttpSocketResponseExtended */
|
2021-01-05 16:45:25 +01:00
|
|
|
private $response;
|
|
|
|
|
|
|
|
public function __construct($message, HttpSocketResponseExtended $response, Throwable $previous = null)
|
|
|
|
{
|
|
|
|
$this->response = $response;
|
|
|
|
parent::__construct($message, 0, $previous);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-05-24 15:17:01 +02:00
|
|
|
* @return HttpSocketResponseExtended
|
2021-01-05 16:45:25 +01:00
|
|
|
*/
|
|
|
|
public function getResponse()
|
|
|
|
{
|
|
|
|
return $this->response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class HttpSocketResponseExtended extends HttpSocketResponse
|
|
|
|
{
|
2022-07-10 08:56:00 +02:00
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isNotModified()
|
|
|
|
{
|
|
|
|
return $this->code == 304;
|
|
|
|
}
|
|
|
|
|
2021-01-05 16:45:25 +01:00
|
|
|
/**
|
|
|
|
* @param string $message
|
|
|
|
* @throws SocketException
|
|
|
|
*/
|
|
|
|
public function parseResponse($message)
|
|
|
|
{
|
|
|
|
parent::parseResponse($message);
|
|
|
|
|
2021-01-13 23:15:04 +01:00
|
|
|
if ($this->body === '') {
|
|
|
|
return; // skip decoding body if is empty
|
|
|
|
}
|
|
|
|
|
2021-01-05 16:45:25 +01:00
|
|
|
$contentEncoding = $this->getHeader('Content-Encoding');
|
|
|
|
if ($contentEncoding === 'gzip' && function_exists('gzdecode')) {
|
|
|
|
$this->body = gzdecode($this->body);
|
|
|
|
if ($this->body === false) {
|
|
|
|
throw new SocketException("Response should be gzip encoded, but gzip decoding failed.");
|
|
|
|
}
|
|
|
|
} else if ($contentEncoding === 'br' && function_exists('brotli_uncompress')) {
|
|
|
|
$this->body = brotli_uncompress($this->body);
|
|
|
|
if ($this->body === false) {
|
|
|
|
throw new SocketException("Response should be brotli encoded, but brotli decoding failed.");
|
|
|
|
}
|
|
|
|
} else if ($contentEncoding) {
|
2021-08-03 09:38:36 +02:00
|
|
|
throw new SocketException("Remote server returns unsupported content encoding '$contentEncoding'.");
|
2021-01-05 16:45:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Decodes JSON string and throws exception if string is not valid JSON.
|
|
|
|
*
|
|
|
|
* @return array
|
2021-05-24 15:17:01 +02:00
|
|
|
* @throws HttpSocketJsonException
|
2021-01-05 16:45:25 +01:00
|
|
|
*/
|
|
|
|
public function json()
|
|
|
|
{
|
2024-03-23 11:30:44 +01:00
|
|
|
if (strlen($this->body) === 0) {
|
|
|
|
throw new HttpSocketJsonException('Could not parse empty response as JSON.', $this);
|
|
|
|
}
|
|
|
|
|
2021-01-05 16:45:25 +01:00
|
|
|
try {
|
2022-10-18 16:41:26 +02:00
|
|
|
return JsonTool::decode($this->body);
|
2021-01-05 16:45:25 +01:00
|
|
|
} catch (Exception $e) {
|
2021-05-24 15:17:01 +02:00
|
|
|
throw new HttpSocketJsonException('Could not parse response as JSON.', $this, $e);
|
2021-01-05 16:45:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Supports response compression and also decodes response as JSON
|
2021-03-04 20:16:50 +01:00
|
|
|
* @method HttpSocketResponseExtended get($uri = null, $query = array(), $request = array())
|
|
|
|
* @method HttpSocketResponseExtended post($uri = null, $data = array(), $request = array())
|
2021-08-03 09:38:36 +02:00
|
|
|
* @method HttpSocketResponseExtended head($uri = null, $query = array(), $request = array())
|
2021-01-05 16:45:25 +01:00
|
|
|
*/
|
|
|
|
class HttpSocketExtended extends HttpSocket
|
|
|
|
{
|
|
|
|
public $responseClass = 'HttpSocketResponseExtended';
|
|
|
|
|
2022-01-20 18:30:36 +01:00
|
|
|
/** @var callable */
|
|
|
|
private $onConnect;
|
|
|
|
|
2021-01-05 16:45:25 +01:00
|
|
|
public function __construct($config = array())
|
|
|
|
{
|
|
|
|
parent::__construct($config);
|
|
|
|
if (isset($config['compress']) && $config['compress']) {
|
|
|
|
$acceptEncoding = $this->acceptedEncodings();
|
|
|
|
if (!empty($acceptEncoding)) {
|
|
|
|
$this->config['request']['header']['Accept-Encoding'] = implode(', ', $this->acceptedEncodings());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-20 18:30:36 +01:00
|
|
|
public function connect()
|
|
|
|
{
|
|
|
|
$connected = parent::connect();
|
|
|
|
if ($this->onConnect) {
|
|
|
|
$handler = $this->onConnect;
|
|
|
|
$handler($this);
|
|
|
|
}
|
|
|
|
return $connected;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set callback method, that will be called after connection to remote server is established.
|
|
|
|
* @param callable $callback
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function onConnectHandler(callable $callback)
|
|
|
|
{
|
|
|
|
$this->onConnect = $callback;
|
|
|
|
}
|
|
|
|
|
2022-01-22 09:31:04 +01:00
|
|
|
/**
|
|
|
|
* @return array|null
|
|
|
|
*/
|
|
|
|
public function getMetaData()
|
|
|
|
{
|
|
|
|
if ($this->connection) {
|
|
|
|
return stream_get_meta_data($this->connection);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-01-06 20:07:55 +01:00
|
|
|
/**
|
|
|
|
* @param array $request
|
|
|
|
* @return HttpSocketResponseExtended
|
|
|
|
*/
|
|
|
|
public function request($request = array())
|
|
|
|
{
|
|
|
|
// Reset last error
|
|
|
|
$this->lastError = [];
|
|
|
|
|
|
|
|
/** @var HttpSocketResponseExtended $response */
|
|
|
|
$response = parent::request($request);
|
|
|
|
if ($response === false) {
|
|
|
|
throw new InvalidArgumentException("Invalid argument provided.");
|
|
|
|
}
|
|
|
|
// Convert connection timeout to SocketException
|
|
|
|
if (!empty($this->lastError)) {
|
2021-02-15 20:06:06 +01:00
|
|
|
throw new SocketException($this->lastError['str']);
|
2021-01-06 20:07:55 +01:00
|
|
|
}
|
|
|
|
return $response;
|
|
|
|
}
|
|
|
|
|
2021-01-05 16:45:25 +01:00
|
|
|
/**
|
|
|
|
* Returns accepted content encodings (compression algorithms)
|
|
|
|
* @return string[]
|
|
|
|
*/
|
|
|
|
private function acceptedEncodings()
|
|
|
|
{
|
|
|
|
$supportedEncoding = [];
|
|
|
|
// Enable brotli compressed responses if PHP has 'brotli_uncompress' method
|
|
|
|
if (function_exists('brotli_uncompress')) {
|
|
|
|
$supportedEncoding[] = 'br';
|
|
|
|
}
|
|
|
|
// Enable gzipped responses if PHP has 'gzdecode' method
|
|
|
|
if (function_exists('gzdecode')) {
|
|
|
|
$supportedEncoding[] = 'gzip';
|
|
|
|
}
|
|
|
|
return $supportedEncoding;
|
|
|
|
}
|
|
|
|
}
|