diff --git a/HttpServer/Client/Client.php b/HttpServer/Client/Client.php index c974b40c..092a9a1a 100644 --- a/HttpServer/Client/Client.php +++ b/HttpServer/Client/Client.php @@ -9,1029 +9,128 @@ declare(strict_types=1); namespace HttpServer\Client; - -use Snowflake\Core\Help; use Exception; -use Snowflake\Exception\ComponentException; -use Snowflake\Snowflake; -use Swoole\Coroutine; use Swoole\Coroutine\Http\Client as SClient; -use Swoole\Coroutine\System; -use Swoole\Coroutine\Client as SCClient; /** * Class Client * @package Snowflake\Snowflake\Http - * @property Http2 $http2 */ -class Client +class Client extends ClientAbstracts { - private string $host = ''; - private array $header = []; - - private int $timeout = 0; - - private ?\Closure $callback = null; - private string $method = 'get'; - - private bool $isSSL = false; - private string $agent = ''; - private string $errorCodeField = ''; - private string $errorMsgField = ''; - private bool $use_swoole = false; - - private string $ssl_cert_file = ''; - private string $ssl_key_file = ''; - private string $ca = ''; - private int $port = 80; - - /** @var string $_message 错误信息 */ - private string $_message = ''; - private string $_data = ''; - - private int $connect_timeout = 1; - - const GET = 'get'; - const PUT = 'put'; - const POST = 'post'; - const DELETE = 'delete'; - const OPTIONS = 'option'; - - /** - * @return int - */ - public function getConnectTimeout(): int - { - return $this->connect_timeout; - } - - /** - * @param int $connect_timeout - */ - public function setConnectTimeout(int $connect_timeout): void - { - $this->connect_timeout = $connect_timeout; - } - - - /** - * @return string - */ - public function getCa(): string - { - return $this->ca; - } - - /** - * @param string $ca - */ - public function setCa(string $ca): void - { - $this->ca = $ca; - } - - - /** - * @return int - */ - public function getPort(): int - { - return $this->port; - } - - /** - * @param int $port - */ - public function setPort(int $port): void - { - $this->port = $port; - } - - - /** - * @return Http2 - * @throws ComponentException - */ - public function getHttp2() - { - return Snowflake::app()->get('http2'); - } - - - /** - * @param $data - */ - public function setData($data) - { - $this->_data = $data; - } - - /** - * @return string - */ - public function getSslCertFile(): string - { - return $this->ssl_cert_file; - } - - /** - * @return bool - */ - public function hasSslCertFile(): bool - { - return !empty($this->ssl_cert_file) && file_exists($this->ssl_cert_file); - } - - /** - * @return bool - */ - public function hasSslKeyFile(): bool - { - return !empty($this->ssl_key_file) && file_exists($this->ssl_key_file); - } - - /** - * @param string $ssl_cert_file - */ - public function setSslCertFile(string $ssl_cert_file) - { - $this->ssl_cert_file = $ssl_cert_file; - } - - /** - * @return string - */ - public function getSslKeyFile(): string - { - return $this->ssl_key_file; - } - - /** - * @param string $ssl_key_file - */ - public function setSslKeyFile(string $ssl_key_file) - { - $this->ssl_key_file = $ssl_key_file; - } - - /** - */ - public static function NewRequest() - { - return new Client(); - } - - /** - * @param string $name - * @return $this - */ - public function setErrorField(string $name) - { - $this->errorCodeField = $name; - return $this; - } - - /** - * @param $bool - * @return $this - */ - public function setUseSwoole($bool) - { - $this->use_swoole = $bool; - if ($this->use_swoole) { - function_exists('setCli') && setCli(true); - } - return $this; - } - - /** - * @param string $name - * @return $this - */ - public function setErrorMsgField(string $name) - { - $this->errorMsgField = $name; - return $this; - } - - /** - * @param string $host - */ - public function setHost(string $host) - { - $this->host = $this->replaceHost($host); - $match_quest = '/^[a-zA-Z\-]+(\.[a-zA-Z\-])+/'; - if (preg_match($match_quest, $this->host)) { - $this->addHeader('Host', $this->host); - } - } - - - /** - * @param $path - * @param array $data - * @param int $type - * @return Result - */ - public function sendTo($path, array $data, $type = SWOOLE_TCP) - { - $client = new SCClient($type); - if (empty($this->host) || empty($this->port)) { - return new Result(['code' => 500, 'message' => 'Host and port is null']); - } - if (!$client->connect($this->host, $this->port, $this->connect_timeout)) { - return new Result(['code' => 500, 'message' => $client->errMsg]); - } - $params['body'] = $data; - $params['path'] = '/' . $this->port . '/' . ltrim($path, '/'); - $params['header']['request_uri'] = $params['path']; - $params['header']['request_method'] = 'receive'; - if ($client->send(serialize($params))) { - $recv = $this->timeout > 0 ? $client->recv($this->timeout) : $client->recv(); - $param = $this->structure(Help::toArray($recv), $data, null, 200); - } else { - $param = new Result(['code' => 500, 'message' => $client->errMsg]); - } - $client->close(); - return $param; - } - - /** - * @param int $sec - * 设置超时时间 - */ - public function setTimeout(int $sec) - { - $this->timeout = $sec; - } - - - /** - * @param $key - * @param $value - */ - public function setHeader($key, $value) - { - $this->header[$key] = $value; - } - - /** - * @param $key - * @param $value - */ - public function addHeader($key, $value) - { - $this->header[$key] = $value; - } - - /** - * @param null $callback - */ - public function setCallback($callback) - { - $this->callback = $callback; - } - - /** - * @param string $method - */ - public function setMethod(string $method) - { - $this->method = $method; - } - - /** - * @param string $agent - */ - public function setAgent(string $agent) - { - $this->agent = $agent; - } - - /** - * @param bool $isSSL - */ - public function setIsSSL(bool $isSSL) - { - $this->isSSL = $isSSL; - if ($this->isSSL) { - $this->port = 443; - } - } - - /** - * @return bool - */ - public function getIsSSL() - { - return $this->isSSL; - } - - /** - * @param $url - * @param array $data - * @return array|mixed|Result - * @throws Exception - */ - private function request($url, $data = []) - { - $data = $this->paramEncode($data); - if ($this->use_swoole) { - return $this->coroutine($this->matchHost($url), $data); - } else { - return $this->useCurl($url, $data); - } - } - - /** - * @return bool - */ - private function isCli() - { - return function_exists('getIsCli') && getIsCli(); - } - - /** - * @param string $string - * @return bool|string - * @throws Exception - */ - private function matchHost($string = '') - { - if (empty($string)) { - return false; - } - - if ($this->isHttp($string)) { - $string = str_replace('http://', '', $string); - $hostAndUrls = explode('/', $string); - - $this->host = array_shift($hostAndUrls); - $string = implode('/', $hostAndUrls); - } else if ($this->isHttps($string)) { - $string = str_replace('https://', '', $string); - $this->setIsSSL(true); - - $hostAndUrls = explode('/', $string); - - $this->host = array_shift($hostAndUrls); - $string = implode('/', $hostAndUrls); - } else if (empty($this->host)) { - $hostAndUrls = explode('/', $string); - $this->host = array_shift($hostAndUrls); - - $string = implode('/', $hostAndUrls); - } - - if (strpos($this->host, ':') !== false) { - [$this->host, $this->port] = explode(':', $this->host); - } - - if (!$this->checkIsIp($this->host) && Coroutine::getuid() > 0) { - $this->host = System::gethostbyname($this->host); - } - - if (!$this->checkIsIp($this->host) && !$this->isDomainName($this->host)) { - throw new Exception('Client Host error.'); - } - - return $string; - } - - /** - * @param $name - * @return bool|mixed - */ - private function isDomainName($name) - { - if (!preg_match('/^[a-zA-Z\-0-9]+(\.[a-zA-Z\-0-9]+)+[^\/]?/', $name, $out)) { - return false; - } - return $out[0]; - } - - /** - * @param $url - * @param $data - * @return array|Result|mixed - * @throws - */ - private function useCurl($url, $data) - { - if ($this->isHttp($url) || $this->isHttps($url)) { - return $this->curl($url, $data); - } - $url = $this->matchHost(ltrim($url, '/')); - if (!empty($this->port)) { - $this->host .= ':' . $this->port; - } - if ($this->isSSL) { - return $this->curl('https://' . $this->host . '/' . $url, $data); - } else { - return $this->curl('http://' . $this->host . '/' . $url, $data); - } - } - - /** - * @param $host - * @return string|string[] - */ - private function replaceHost($host) - { - if ($this->isHttp($host)) { - return str_replace('http://', '', $host); - } - if ($this->isHttps($host)) { - return str_replace('https://', '', $host); - } - return $host; - } - - /** - * @param $url - * @return false|int - */ - private function checkIsIp($url) - { - return preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $url); - } - - /** - * @param $url - * @return bool - */ - private function isHttp($url) - { - return strpos($url, 'http://') === 0; - } - - /** - * @param $url - * @return bool - */ - private function isHttps($url) - { - return strpos($url, 'https://') === 0; - } - - - /** - * @param $url - * @param array $data - * @return array|mixed|Result - * @throws Exception - * 使用swoole协程方式请求 - */ - private function coroutine($url, $data = []) - { - try { - $client = $this->generate_client($this->host, $url, $data); - if ($client->statusCode < 0) { - throw new Exception($client->errMsg); - } - unset($this->_data); - - $body = $this->resolve($client->getHeaders(), $client->body); - if (!in_array($client->getStatusCode(), [200, 201])) { - if (is_string($body)) { - $message = 'Request error code ' . $client->getStatusCode(); - } else { - $message = $this->searchMessageByData($body); - } - $response['code'] = $client->getStatusCode(); - $response['message'] = $message; - $response['data'] = $body; - $response['header'] = $client->getHeaders(); - - $response = new Result($response); - } else { - $response = $this->structure($body, $data, $client->getHeaders()); - } - } catch (\Throwable $exception) { - $response['code'] = 500; - $response['message'] = $exception->getMessage(); - $response['data'] = array_slice($exception->getTrace(), 0, 6); - $response['header'] = []; - - $response = new Result($response); - } - return $response; - } - - /** - * @return int - */ - private function getHostPort() - { - if (!empty($this->port)) { - return $this->port; - } - $port = 80; - if ($this->isSSL) $port = 443; - return $port; - } - - /** - * @param $host - * @param $url - * @param $data - * @return SClient - */ - private function generate_client($host, $url, $data = []) - { - $client = new SClient($host, $this->getHostPort(), $this->isSSL); - if (strpos($url, '/') !== 0) { - $url = '/' . $url; - } - - $client->set($this->settings()); - if (!empty($this->agent)) { - $this->header['User-Agent'] = $this->agent; - } - if (!empty($this->header)) { - $client->setHeaders($this->header); - } - $client->setMethod(strtoupper($this->method)); - if (strtolower($this->method) == self::GET && !empty($data)) { - $url .= '?' . $data; - } else { - $this->_data = $this->mergeParams($data); - } - - if (!empty($this->_data)) { - $client->setData($this->_data); - } - $client->execute($url); - $client->close(); - return $client; - } - - /** - * @param $newData - * @return mixed - */ - private function mergeParams($newData) - { - if (empty($this->_data)) { - return $this->toRequest($newData); - } else if (empty($newData)) { - return $this->toRequest($this->_data); - } - - $newData = Help::toArray($newData); - $array = Help::toArray($this->_data); - - $params = array_merge($array, $newData); - - return $this->toRequest($params); - } - - - /** - * @param $data - * @return false|mixed|string - */ - private function toRequest($data) - { - if (is_string($data)) { - return $data; - } - - $contentType = 'application/x-www-form-urlencoded'; - if (isset($this->header['Content-Type'])) { - $contentType = $this->header['Content-Type']; - } else if (isset($this->header['content-type'])) { - $contentType = $this->header['content-type']; - } - - if (strpos($contentType, 'json') !== false) { - return Help::toJson($data); - } else if (strpos($contentType, 'xml') !== false) { - return Help::toXml($data); - } else { - return http_build_query($data); - } - } - - /** - * @return array - */ - private function settings() - { - $sslCert = $this->getSslCertFile(); - $sslKey = $this->getSslKeyFile(); - $sslCa = $this->getCa(); - - $params = []; - if ($this->connect_timeout > 0) { - $params['timeout'] = $this->connect_timeout; - } - if (empty($sslCert) || empty($sslKey) || empty($sslCa)) { - return $params; - } - - $params['ssl_host_name'] = $this->host; - $params['ssl_cert_file'] = $this->getSslCertFile(); - $params['ssl_key_file'] = $this->getSslKeyFile(); - $params['ssl_verify_peer'] = true; - $params['ssl_cafile'] = $sslCa; - - return $params; - } - - /** - * @param $url - * @param array $data - * @return array|mixed|Result - */ - private function curl($url, $data = []) - { - try { - $output = $this->curlParse($url, $this->mergeParams($data)); - if ($output === FALSE) { - return new Result(['code' => 500, 'message' => $output]); - } - [$header, $body, $status] = $this->explode($output); - if (!in_array($status, [200, 201])) { - $data = new Result(['code' => $status, 'message' => $body, 'header' => $header]); - } else { - $data = $this->structure($body, $data, $header); - } - return $data; - } catch (\Throwable $exception) { - $response['code'] = 500; - $response['message'] = $exception->getMessage(); - $response['data'] = array_slice($exception->getTrace(), 0, 6); - $response['header'] = []; - return new Result($response); - } - } - - /** - * @param $url - * @param $data - * @return bool|string - * @throws Exception - */ - private function curlParse($url, $data) - { - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $this->createRequestUrl($url, $data)); - if ($this->timeout > 0) { - curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); // 超时设置 - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout); // 超时设置 - } - curl_setopt($ch, CURLOPT_HEADER, true); - - if ($headers = $this->parseHeaderMat()) { - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - } - if (!empty($this->agent)) { - curl_setopt($ch, CURLOPT_USERAGENT, $this->agent); - } - if (file_exists($cert = $this->getSslCertFile())) { - curl_setopt($ch, CURLOPT_SSLCERT, $cert); - } - if (file_exists($key = $this->getSslKeyFile())) { - curl_setopt($ch, CURLOPT_SSLKEY, $key); - } - - curl_setopt($ch, CURLOPT_NOBODY, FALSE); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);//返回内容 - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);// 跟踪重定向 - curl_setopt($ch, CURLOPT_ENCODING, 'gzip,deflate'); - - if ($this->method == self::POST) { - curl_setopt($ch, CURLOPT_POST, 1); - } - - if ($this->method != self::GET) { - curl_setopt($ch, CURLOPT_POSTFIELDS, $data); - } - - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($this->method)); - $output = curl_exec($ch); - if ($output === false) { - throw new Exception(curl_error($ch)); - } - curl_close($ch); - return $output; - } - - - /** - * @param $url - * @param $params - * @return array|mixed|Result - * 上传文件 - */ - public function upload($url, $params) - { - try { - $this->method = self::POST; - $output = $this->curlParse($url, $params); - if ($output === FALSE) { - return new Result(['code' => 500, 'message' => $output]); - } - [$header, $body, $status] = $this->explode($output); - if ($status != 200 && $status != 201) { - $data = new Result(['code' => $status, 'message' => $body, 'header' => $header]); - } else { - $data = $this->structure($body, $params, $header); - } - return $data; - } catch (\Throwable $exception) { - $response['code'] = 500; - $response['message'] = $exception->getMessage(); - $response['data'] = array_slice($exception->getTrace(), 0, 6); - $response['header'] = []; - return new Result($response); - } - } - - - /** - * @param $output - * @return array - */ - private function explode($output) - { - [$header, $body] = explode("\r\n\r\n", $output, 2); - if ($header == 'HTTP/1.1 100 Continue') { - [$header, $body] = explode("\r\n\r\n", $body, 2); - } else if (strpos($body, "\r\n\r\n") !== false) { - [$header, $body] = explode("\r\n\r\n", $body, 2); - } - $header = explode("\r\n", $header); - - unset($output); - - $status = (int)explode(' ', trim($header[0]))[1]; - $header = $this->headerFormat($header); - - return [$header, $this->resolve($header, $body), $status]; - } - - /** - * @param $url - * @param $data - * @return string - */ - private function createRequestUrl($url, $data) - { - if ($this->isGet()) { - return $url . '?' . $data; - } - return $url; - } - - /** - * @param $data - * @param $body - * @return mixed - */ - private function resolve($data, $body) - { - if (is_array($body)) { - return $body; - } - $type = $data['content-type'] ?? $data['Content-Type'] ?? 'text/html'; - if (strpos($type, 'text/html') !== false) { - return $body; - } else if (strpos($type, 'json') !== false) { - return json_decode($body, true); - } else if (strpos($type, 'xml') !== false) { - return Help::xmlToArray($body); - } else if (strpos($type, 'plain') !== false) { - return Help::toArray($body); - } - return $body; - } - - /** - * @param $headers - * @return array - */ - private function headerFormat($headers) - { - $_tmp = []; - foreach ($headers as $key => $val) { - $trim = explode(': ', trim($val)); - - $_tmp[strtolower($trim[0])] = $trim[1] ?? ''; - } - return $_tmp; - } - - /** - * @param $body - * @param $_data - * @param $header - * @param $statusCode - * @return array|mixed|Result - * 构建返回体 - */ - private function structure($body, $_data, $header = [], $statusCode = 200) - { - $this->setIsSSL(false); - $this->setHeaders([]); - - if ($this->callback !== NULL) { - $result = call_user_func($this->callback, $body, $_data, $header); - $this->setCallback(null); - - return $result; - } - if (is_string($body)) { - $result['code'] = 0; - $result['message'] = ''; - } else { - $result['code'] = $body[$this->errorCodeField] ?? 0; - $result['message'] = $this->searchMessageByData($body); - } - $result['data'] = $body; - $result['header'] = $header; - $result['httpStatus'] = $statusCode; - - return new Result($result); - } - - /** - * @param $body - * @return array|mixed|string - */ - private function searchMessageByData($body) - { - $parent = []; - if (empty($this->errorMsgField)) { - return 'system success.'; - } - $explode = explode('.', $this->errorMsgField); - if (!isset($body[$explode[0]])) { - return 'system success.'; - } - foreach ($explode as $item) { - if (empty($item)) { - continue; - } - if (empty($parent)) { - $parent = $body[$item]; - continue; - } - if (is_string($parent) || !isset($parent[$item])) { - break; - } - $parent = $parent[$item]; - } - return !empty($parent) ? $parent : 'system success.'; - } - - - /** - * @return bool - * check isPost Request - */ - public function isPost() - { - return strtolower($this->method) === self::POST; - } - - /** - * @return bool - * - * check isGet Request - */ - public function isGet() - { - return strtolower($this->method) === self::GET; - } - - /** - * @param $arr - * - * @return array|string - * 将请求参数进行编码 - */ - private function paramEncode($arr) - { - if (!is_array($arr)) { - return $arr; - } - $_tmp = []; - foreach ($arr as $Key => $val) { - $_tmp[$Key] = $val; - } - if ($this->isGet()) { - return http_build_query($_tmp); - } - return $_tmp; - } - - /** - * @param $url - * @param array $data - * @return array|mixed|Result - * @throws - */ - public function post($url, $data = []) - { - $this->setMethod(self::POST); - return $this->request($url, $data); - } - - - /** - * @param $url - * @param array $data - * @return array|mixed|Result - * @throws - */ - public function put($url, $data = []) - { - $this->setMethod(self::PUT); - return $this->request($url, $data); - } - - /** - * @param $url - * @param array $data - * @return array|mixed|Result - * @throws - */ - public function get($url, $data = []) - { - $this->setMethod(self::GET); - return $this->request($url, $data); - } - - /** - * @param $url - * @param array $data - * @return array|mixed|Result - * @throws Exception - */ - public function option($url, $data = []) - { - $this->setMethod(self::OPTIONS); - return $this->request($url, $data); - } - - /** - * @param $url - * @param array $data - * @return array|mixed|Result - * @throws Exception - */ - public function delete($url, $data = []) - { - $this->setMethod(self::DELETE); - return $this->request($url, $data); - } - - /** - * @param $url - * @param array $data - * @return array|mixed|Result - * @throws Exception - */ - public function send($url, $data = []) - { - return $this->request($url, $data); - } - - /** - * @return array - */ - private function parseHeaderMat() - { - if ($this->use_swoole) { - return $this->header; - } - $headers = []; - foreach ($this->header as $key => $val) { - $header = $key . ':' . $val; - if (in_array($header, $headers)) { - continue; - } - $headers[] = $header; - } - $this->header = []; - return $headers; - } - - /** - * @param array $headers - * @return array - */ - public function setHeaders(array $headers) - { - if (empty($headers)) { - return []; - } - foreach ($headers as $key => $val) { - $this->header[$key] = $val; - } - return $this->header; - } + /** + * @param string $method + * @param $url + * @param array $data + * @return array|mixed|Result + * @throws Exception + */ + public function request(string $method, $url, $data = []) + { + return $this->setMethod($method) + ->coroutine( + $this->matchHost($url), + $this->paramEncode($data) + ); + } + + + /** + * @param $url + * @param array $data + * @return array|mixed|Result + * @throws Exception + * 使用swoole协程方式请求 + */ + private function coroutine($url, $data = []) + { + try { + $client = $this->generate_client($data, ...$url); + if ($client->statusCode < 0) { + throw new Exception($client->errMsg); + } + $this->setData(''); + $body = $this->resolve($client->getHeaders(), $client->body); + if (in_array($client->getStatusCode(), [200, 201])) { + return $this->structure($body, $data, $client->getHeaders()); + } + if (is_string($body)) { + $message = 'Request error code ' . $client->getStatusCode(); + } else { + $message = $this->searchMessageByData($body); + } + return $this->fail($client->getStatusCode(), $message, $body, $client->getHeaders()); + } catch (\Throwable $exception) { + return $this->fail(500, $exception->getMessage(), [ + 'file' => $exception->getFile(), + 'line' => $exception->getLine() + ], []); + } + } + + /** + * @param $data + * @param $host + * @param $isHttps + * @param $path + * @return SClient + */ + private function generate_client($data, $host, $isHttps, $path) + { + $port = $isHttps ? 443 : $this->getPort(); + $client = new SClient($host, $port, $this->isSSL()); + + if (strpos($path, '/') !== 0) { + $path = '/' . $path; + } + + $client->set($this->settings()); + if (!empty($this->getAgent())) { + $this->addHeader('User-Agent', $this->getAgent()); + } + + $client->setHeaders($this->getHeader()); + $client->setMethod(strtoupper($this->getMethod())); + + if (strtolower($this->getMethod()) == self::GET && !empty($data)) { + $path .= '?' . $data; + } else { + $this->setData($this->mergeParams($data)); + } + + if (!empty($this->getData())) { + $client->setData($this->getData()); + } + + $client->execute($path); + $client->close(); + return $client; + } + + /** + * @return array + */ + private function settings() + { + $sslCert = $this->getSslCertFile(); + $sslKey = $this->getSslKeyFile(); + $sslCa = $this->getCa(); + + $params = []; + if ($this->getConnectTimeout() > 0) { + $params['timeout'] = $this->getConnectTimeout(); + } + if (empty($sslCert) || empty($sslKey) || empty($sslCa)) { + return $params; + } + + $params['ssl_host_name'] = $this->getHost(); + $params['ssl_cert_file'] = $this->getSslCertFile(); + $params['ssl_key_file'] = $this->getSslKeyFile(); + $params['ssl_verify_peer'] = true; + $params['ssl_cafile'] = $sslCa; + + return $params; + } } diff --git a/HttpServer/Client/ClientAbstracts.php b/HttpServer/Client/ClientAbstracts.php new file mode 100644 index 00000000..360ccfd4 --- /dev/null +++ b/HttpServer/Client/ClientAbstracts.php @@ -0,0 +1,797 @@ +request(self::POST, $url, $data); + } + + + /** + * @param $url + * @param array $data + * @return array|mixed|Result + * @throws + */ + public function put($url, $data = []) + { + return $this->request(self::PUT, $url, $data); + } + + + /** + * @param string $path + * @param array $params + * @return mixed + */ + public function head(string $path, array $params = []) + { + return $this->request(self::HEAD, $path, $params); + } + + + /** + * @param $url + * @param array $data + * @return array|mixed|Result + * @throws + */ + public function get($url, $data = []) + { + return $this->request(self::GET, $url, $data); + } + + /** + * @param $url + * @param array $data + * @return array|mixed|Result + * @throws Exception + */ + public function option($url, $data = []) + { + return $this->request(self::OPTIONS, $url, $data); + } + + /** + * @param $url + * @param array $data + * @return array|mixed|Result + * @throws Exception + */ + public function delete($url, $data = []) + { + return $this->request(self::DELETE, $url, $data); + } + + /** + * @param string $path + * @param array $params + * @return mixed + * @throws Exception + */ + public function options(string $path, array $params = []) + { + return $this->request(self::OPTIONS, $path, $params); + + } + + /** + * @param string $path + * @param array $params + * @return mixed + * @throws Exception + */ + public function upload(string $path, array $params = []) + { + return $this->request(self::OPTIONS, $path, $params); + } + + + /** + * @return string + */ + public function getHost(): string + { + return $this->host; + } + + /** + * @return int + */ + protected function getHostPort() + { + if (!empty($this->getPort())) { + return $this->getPort(); + } + $port = 80; + if ($this->isSSL()) $port = 443; + return $port; + } + + + /** + * @param string $host + */ + public function setHost(string $host): void + { + if (!preg_match('/(\d{1,3}\.){4}/', $host . '.')) { + $this->addHeader('Host', $host); + $this->host = System::gethostbyname($host); + } else { + $this->host = $host; + } + } + + /** + * @return array + */ + public function getHeader(): array + { + return $this->header; + } + + /** + * @param array $header + */ + public function setHeader(array $header): void + { + $this->header = $header; + } + + + /** + * @param array $headers + * @return array + */ + public function setHeaders(array $headers) + { + if (empty($headers)) { + return []; + } + foreach ($headers as $key => $val) { + $this->header[$key] = $val; + } + return $this->header; + } + + /** + * @param $key + * @param $value + */ + public function addHeader($key, $value) + { + $this->header[$key] = $value; + } + + /** + * @return int + */ + public function getTimeout(): int + { + return $this->timeout; + } + + /** + * @param int $timeout + */ + public function setTimeout(int $timeout): void + { + $this->timeout = $timeout; + } + + /** + * @return Closure|null + */ + public function getCallback(): ?Closure + { + return $this->callback; + } + + /** + * @param Closure|null $callback + */ + public function setCallback(?Closure $callback): void + { + $this->callback = $callback; + } + + /** + * @return string + */ + public function getMethod(): string + { + return $this->method; + } + + /** + * @param string $method + * @return $this + */ + public function setMethod(string $method): self + { + $this->method = $method; + return $this; + } + + /** + * @return bool + */ + public function isSSL(): bool + { + return $this->isSSL; + } + + /** + * @param bool $isSSL + */ + public function setIsSSL(bool $isSSL): void + { + $this->isSSL = $isSSL; + } + + /** + * @return string + */ + public function getAgent(): string + { + return $this->agent; + } + + /** + * @param string $agent + */ + public function setAgent(string $agent): void + { + $this->agent = $agent; + } + + /** + * @return string + */ + public function getErrorCodeField(): string + { + return $this->errorCodeField; + } + + /** + * @param string $errorCodeField + */ + public function setErrorCodeField(string $errorCodeField): void + { + $this->errorCodeField = $errorCodeField; + } + + /** + * @return string + */ + public function getErrorMsgField(): string + { + return $this->errorMsgField; + } + + /** + * @param string $errorMsgField + */ + public function setErrorMsgField(string $errorMsgField): void + { + $this->errorMsgField = $errorMsgField; + } + + /** + * @return bool + */ + public function isUseSwoole(): bool + { + return $this->use_swoole; + } + + /** + * @param bool $use_swoole + */ + public function setUseSwoole(bool $use_swoole): void + { + $this->use_swoole = $use_swoole; + } + + /** + * @return string + */ + public function getSslCertFile(): string + { + return $this->ssl_cert_file; + } + + /** + * @param string $ssl_cert_file + */ + public function setSslCertFile(string $ssl_cert_file): void + { + $this->ssl_cert_file = $ssl_cert_file; + } + + /** + * @return string + */ + public function getSslKeyFile(): string + { + return $this->ssl_key_file; + } + + /** + * @param string $ssl_key_file + */ + public function setSslKeyFile(string $ssl_key_file): void + { + $this->ssl_key_file = $ssl_key_file; + } + + /** + * @return string + */ + public function getCa(): string + { + return $this->ca; + } + + /** + * @param string $ca + */ + public function setCa(string $ca): void + { + $this->ca = $ca; + } + + /** + * @return int + */ + public function getPort(): int + { + return $this->port; + } + + /** + * @param int $port + */ + public function setPort(int $port): void + { + $this->port = $port; + } + + /** + * @return string + */ + public function getMessage(): string + { + return $this->_message; + } + + /** + * @param string $message + */ + public function setMessage(string $message): void + { + $this->_message = $message; + } + + /** + * @return string + */ + public function getData(): string + { + return $this->_data; + } + + /** + * @param string $data + */ + public function setData(string $data): void + { + $this->_data = $data; + } + + /** + * @return int + */ + public function getConnectTimeout(): int + { + return $this->connect_timeout; + } + + /** + * @param int $connect_timeout + */ + public function setConnectTimeout(int $connect_timeout): void + { + $this->connect_timeout = $connect_timeout; + } + + + /** + * @param $host + * @return string|string[] + */ + protected function replaceHost($host) + { + if ($this->isHttp($host)) { + return str_replace('http://', '', $host); + } + if ($this->isHttps($host)) { + return str_replace('https://', '', $host); + } + return $host; + } + + + /** + * @param $url + * @return false|int + */ + protected function checkIsIp($url) + { + return preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $url); + } + + /** + * @param $url + * @return bool + */ + protected function isHttp($url) + { + return strpos($url, 'http://') === 0; + } + + /** + * @param $url + * @return bool + */ + protected function isHttps($url) + { + return strpos($url, 'https://') === 0; + } + + + /** + * @param $newData + * @return mixed + */ + protected function mergeParams($newData) + { + if (empty($this->getData())) { + return $this->toRequest($newData); + } else if (empty($newData)) { + return $this->toRequest($this->getData()); + } + + $newData = Help::toArray($newData); + $array = Help::toArray($this->getData()); + + $params = array_merge($array, $newData); + + return $this->toRequest($params); + } + + + /** + * @param $data + * @return false|mixed|string + */ + protected function toRequest($data) + { + if (is_string($data)) { + return $data; + } + $contentType = 'application/x-www-form-urlencoded'; + if (isset($this->header['Content-Type'])) { + $contentType = $this->header['Content-Type']; + } else if (isset($this->header['content-type'])) { + $contentType = $this->header['content-type']; + } + if (strpos($contentType, 'json') !== false) { + return Help::toJson($data); + } else if (strpos($contentType, 'xml') !== false) { + return Help::toXml($data); + } else { + return http_build_query($data); + } + } + + + /** + * @param $data + * @param $body + * @return mixed + */ + protected function resolve($data, $body) + { + if (is_array($body)) { + return $body; + } + $type = $data['content-type'] ?? $data['Content-Type'] ?? 'text/html'; + if (strpos($type, 'text/html') !== false) { + return $body; + } else if (strpos($type, 'json') !== false) { + return json_decode($body, true); + } else if (strpos($type, 'xml') !== false) { + return Help::xmlToArray($body); + } else if (strpos($type, 'plain') !== false) { + return Help::toArray($body); + } + return $body; + } + + + /** + * @param $body + * @param $_data + * @param $header + * @param $statusCode + * @return array|mixed|Result + * 构建返回体 + */ + protected function structure($body, $_data, $header = [], $statusCode = 200) + { + if ($this->callback instanceof Closure) { + $result = call_user_func($this->callback, $body, $_data, $header); + } else { + $result = $this->parseResult($body, $header, $statusCode); + } + return $result; + } + + + /** + * @param $body + * @param $header + * @param $statusCode + * @return Result + */ + private function parseResult($body, $header, $statusCode) + { + if (is_string($body)) { + $result['code'] = 0; + $result['message'] = ''; + } else { + $result['code'] = $body[$this->errorCodeField] ?? 0; + $result['message'] = $this->searchMessageByData($body); + } + $result['data'] = $body; + $result['header'] = $header; + $result['httpStatus'] = $statusCode; + return new Result($result); + } + + + /** + * @param $body + * @return array|mixed|string + */ + protected function searchMessageByData($body) + { + $parent = []; + if (empty($this->errorMsgField)) { + return 'system success.'; + } + $explode = explode('.', $this->errorMsgField); + if (!isset($body[$explode[0]])) { + return 'system success.'; + } + foreach ($explode as $item) { + if (empty($item)) { + continue; + } + if (empty($parent)) { + $parent = $body[$item]; + continue; + } + if (is_string($parent) || !isset($parent[$item])) { + break; + } + $parent = $parent[$item]; + } + return !empty($parent) ? $parent : 'system success.'; + } + + + /** + * @return bool + * check isPost Request + */ + public function isPost() + { + return strtolower($this->method) === self::POST; + } + + + /** + * @return bool + * + * check isGet Request + */ + public function isGet() + { + return strtolower($this->method) === self::GET; + } + + /** + * @param $arr + * + * @return array|string + * 将请求参数进行编码 + */ + protected function paramEncode($arr) + { + if (!is_array($arr)) { + return $arr; + } + $_tmp = []; + foreach ($arr as $Key => $val) { + $_tmp[$Key] = $val; + } + if ($this->isGet()) { + return http_build_query($_tmp); + } + return $_tmp; + } + + + /** + * @param string $string + * @return array|string[] + */ + protected function matchHost(string $string) + { + if (($parse = isUrl($string, true)) === false) { + return $this->defaultString($string); + } + [$isHttps, $domain, $port, $path] = $parse; + if (strpos($domain, ':' . $port) !== false) { + $domain = str_replace(':' . $port, '', $domain); + } + if (isIp($domain)) { + $this->host = $domain; + } else { + $this->host = System::gethostbyname($domain) ?? $domain; + } + + if (!empty($this->port)) { + $port = $this->port; + } + if (!empty($port) && $port != 443) { + $this->host .= ':' . $port; + } + + $this->header['Host'] = $domain; + if (strpos($path, '/') !== 0) { + $path = '/' . $path; + } + return [$this->host, $isHttps, $path]; + } + + + /** + * @param $string + * @return array + */ + private function defaultString($string) + { + $host = $this->getHost(); + if (!empty($this->port) && $this->port != 443) { + $host .= ':' . $this->getPort(); + } + if ($string == '/') { + $string = ''; + } else if (strpos($string, '/') !== 0) { + $string = '/' . $string; + } + return [$host, false, $string]; + } + + + /** + * @param $path + * @param $params + * @return string + */ + protected function joinGetParams($path, $params) + { + if (empty($params)) { + return $path; + } + if (!is_string($params)) { + $params = http_build_query($params); + } + if (strpos($path, '?') !== false) { + [$path, $getParams] = explode('?', $path); + } + if (!isset($getParams) || empty($getParams)) { + return $path . '?' . $params; + } + return $path . '?' . $params . '&' . $getParams; + } + + + /** + * @param $code + * @param $message + * @param $data + * @param $header + * @return Result + */ + protected function fail($code, $message, $data = [], $header = []) + { + return new Result([ + 'code' => $code, + 'message' => $message, + 'data' => $data, + 'header' => $header, + ]); + } + + +} \ No newline at end of file diff --git a/HttpServer/Client/Curl.php b/HttpServer/Client/Curl.php index 1bd4d9ed..34fbca09 100644 --- a/HttpServer/Client/Curl.php +++ b/HttpServer/Client/Curl.php @@ -4,823 +4,224 @@ declare(strict_types=1); namespace HttpServer\Client; -use Closure; use Exception; -use Snowflake\Core\Help; -use Snowflake\Snowflake; -use Swoole\Coroutine; -use Swoole\Coroutine\System; /** * Class Curl * @package HttpServer\Client */ -class Curl +class Curl extends ClientAbstracts { - const POST = 'post'; - const GET = 'get'; - const DELETE = 'delete'; - const OPTIONS = 'options'; - const HEAD = 'head'; - const PUT = 'put'; - - - private array $curl_multi = []; - - private array $headers = [ - 'Connection' => 'Keep-Alive', - 'Keep-Alive' => '300' - ]; - - /** @var ?Closure */ - private ?\Closure $callback; - - /** @var string */ - private string $errorCodeField = ''; - - /** @var string */ - private string $errorMsgField = ''; - - /** @var int */ - private int $timeout = -1; - - /** @var int */ - private int $connection_timeout = 2; - - /** @var bool */ - private bool $useKeepAlive = true; - - /** @var string */ - private string $agent = ''; - - /** @var string */ - private string $ssl_key = ''; - - /** @var string */ - private string $ssl_cert = ''; - - /** @var string */ - private string $ssl_ca = ''; - - /** @var string */ - private string $host = '127.0.0.1'; - - /** @var int */ - private int $port = 9958; - - /** @var bool */ - private bool $isSsl = true; - - - /** @var Curl */ - private static Curl $instance; - - /** - * @return array|Closure - */ - public function getCallback() - { - return $this->callback; - } - - /** - * @param Closure $callback - */ - public function setCallback(Closure $callback): void - { - $this->callback = $callback; - } - - /** - * @return string - */ - public function getErrorCodeField(): string - { - return $this->errorCodeField; - } - - /** - * @param string $errorCodeField - */ - public function setErrorCodeField(string $errorCodeField): void - { - $this->errorCodeField = $errorCodeField; - } - - /** - * @return string - */ - public function getErrorMsgField(): string - { - return $this->errorMsgField; - } - - /** - * @param string $errorMsgField - */ - public function setErrorMsgField(string $errorMsgField): void - { - $this->errorMsgField = $errorMsgField; - } - - /** - * @return int - */ - public function getTimeout(): int - { - return $this->timeout; - } - - /** - * @param int $timeout - */ - public function setTimeout(int $timeout): void - { - $this->timeout = $timeout; - } - - /** - * @return int - */ - public function getConnectionTimeout(): int - { - return $this->connection_timeout; - } - - /** - * @param int $connection_timeout - */ - public function setConnectionTimeout(int $connection_timeout): void - { - $this->connection_timeout = $connection_timeout; - } - - /** - * @return string - */ - public function getAgent(): string - { - return $this->agent; - } - - /** - * @param string $agent - */ - public function setAgent(string $agent): void - { - $this->agent = $agent; - } - - /** - * @return string - */ - public function getSslKey(): string - { - return $this->ssl_key; - } - - /** - * @param string $ssl_key - * @throws Exception - */ - public function setSslKey(string $ssl_key): void - { - $ssl_key = realpath($ssl_key); - if (!file_exists($ssl_key)) { - throw new Exception('Ssl file not exists.'); - } - $this->ssl_key = $ssl_key; - } - - /** - * @return string - */ - public function getSslCert(): string - { - return $this->ssl_cert; - } - - /** - * @param string $ssl_cert - * @throws Exception - */ - public function setSslCert(string $ssl_cert): void - { - $ssl_cert = realpath($ssl_cert); - if (!file_exists($ssl_cert)) { - throw new Exception('Ssl file not exists.'); - } - $this->ssl_cert = $ssl_cert; - } - - /** - * @return string - */ - public function getSslCa(): string - { - return $this->ssl_ca; - } - - /** - * @param string $ssl_ca - * @throws Exception - */ - public function setSslCa(string $ssl_ca): void - { - $ssl_ca = realpath($ssl_ca); - if (!file_exists($ssl_ca)) { - throw new Exception('Ssl file not exists.'); - } - $this->ssl_ca = $ssl_ca; - } - - - /** - * @return string[] - */ - public function getHeaders(): array - { - return $this->headers; - } - - /** - * @param string[] $headers - */ - public function setHeaders(array $headers): void - { - $this->headers = $headers; - } - - /** - * @param $key - * @param $value - */ - public function setHeader($key, $value): void - { - $this->headers[$key] = $value; - } - - - /** - * @return bool - */ - public function isSsl(): bool - { - return $this->isSsl; - } - - /** - * @param bool $isSsl - */ - public function setIsSsl(bool $isSsl): void - { - $this->isSsl = $isSsl; - if ($this->isSsl()) { - $this->port = 443; - } - } - - - /** - * @return string - */ - public function getHost(): string - { - return $this->host; - } - - /** - * @param string $host - */ - public function setHost(string $host): void - { - if (!preg_match('/(\d{1,3}\.){4}/', $host . '.')) { - $this->setHeader('Host', $host); - $this->host = System::gethostbyname($host); - } else { - $this->host = $host; - } - } - - /** - * @return string - */ - public function getPort(): int - { - if (empty($this->port)) { - return 80; - } - return $this->port; - } - - /** - * @param int $port - */ - public function setPort(int $port): void - { - $this->port = $port; - } - - - /** - * @param int $keepLive - */ - public function setKeepAlive(int $keepLive) - { - if ($keepLive < 0) { - $keepLive = 300; - } - $this->headers['Keep-Alive'] = $keepLive; - } - - /** - * @return bool - */ - public function isUseKeepAlive(): bool - { - return $this->useKeepAlive; - } - - /** - * @param bool $useKeepAlive - */ - public function setUseKeepAlive(bool $useKeepAlive): void - { - $this->useKeepAlive = $useKeepAlive; - } - - /** - * @return Curl - */ - public static function NewRequest() - { - if (!(static::$instance instanceof Curl)) { - static::$instance = new Curl(); - } - static::$instance->setHeaders([]); - static::$instance->callback = null; - return static::$instance; - } - - - /** - * @param $path - * @param array $params - * @return bool|string - * @throws Exception - */ - public function get($path, $params = []) - { - return $this->request($this->joinGetParams($path, $params), self::GET); - } - - - /** - * @param $path - * @param array $params - * @return bool|string - * @throws Exception - */ - public function post($path, $params = []) - { - return $this->request($path, self::POST, $params); - } - - /** - * @param $path - * @param array $params - * @return bool|string - * @throws Exception - */ - public function delete($path, $params = []) - { - return $this->request($path, self::DELETE, $params); - } - - /** - * @param $path - * @param array $params - * @return bool|string - * @throws Exception - */ - public function put($path, $params = []) - { - return $this->request($path, self::PUT, $params); - } - - /** - * @param $path - * @param array $params - * @return bool|string - * @throws Exception - */ - public function options($path, $params = []) - { - return $this->request($path, self::OPTIONS, $params); - } - - - /** - * @param $path - * @param $method - * @param array $params - * @return bool|string - * @throws Exception - */ - private function request($path, $method, $params = []) - { - return $this->execute($this->getCurlHandler($path, $method, $params)); - } - - - /** - * @param $path - * @param array $methods - */ - public function clean($path, array $methods) - { - [$host, $isHttps, $path] = $this->matchHost($path); - foreach ($methods as $method) { - $hash = hash('sha256', $host . $path . $method); - if (!isset($this->curl_multi[$hash])) { - continue; - } - unset($this->curl_multi[$hash]); - } - } - - - /** - * @param $path - * @param $method - * @param $params - * @return mixed|resource - * @throws Exception - */ - private function getCurlHandler($path, $method, $params) - { - [$host, $isHttps, $path] = $this->matchHost($path); - - $hash = hash('sha256', $host . $path . $method); - if (isset($this->curl_multi[$hash])) { - return $this->curl_multi[$hash]; - } - - $resource = $this->do(curl_init($host . $path), $host . $path, $method); - if ($method === self::POST && !empty($params)) { - curl_setopt($resource, CURLOPT_POSTFIELDS, HttpParse::parse($params)); - } - - if ($isHttps !== false) { - return $this->curl_multi[$hash] = $this->curlHandlerSslSet($resource); - } - return $this->curl_multi[$hash] = $resource; - } - - - /** - * @param $path - * @param $params - * @return mixed|resource - * @throws Exception - */ - public function upload($path, $params) - { - [$host, $isHttps, $path] = $this->matchHost($path); - $resource = $this->do(curl_init($host . $path), $host . $path, self::POST); - - @curl_setopt($resource, CURLOPT_POSTFIELDS, $params); - - if ($isHttps !== false) { - return $this->execute($this->curlHandlerSslSet($resource)); - } - return $this->execute($resource); - } - - - /** - * @param $resource - * @return bool - * @throws Exception - */ - private function curlHandlerSslSet($resource) - { - if (!empty($this->ssl_key)) { - if (!file_exists($this->ssl_key)) { - throw new Exception('SSL protocol certificate not found.'); - } - curl_setopt($resource, CURLOPT_SSLKEY, $this->getSslKey()); - } - if (!empty($this->ssl_cert)) { - if (!!file_exists($this->ssl_cert)) { - throw new Exception('SSL protocol certificate not found.'); - } - curl_setopt($resource, CURLOPT_SSLCERT, $this->getSslCert()); - } - return $resource; - } - - - /** - * @param $resource - * @param $path - * @param $method - * @return resource - * @throws Exception - */ - private function do($resource, $path, $method) - { - curl_setopt($resource, CURLOPT_TIMEOUT, $this->timeout); // 超时设置 - curl_setopt($resource, CURLOPT_CONNECTTIMEOUT, $this->connection_timeout); // 超时设置 - - curl_setopt($resource, CURLOPT_HEADER, true); - curl_setopt($resource, CURLOPT_FAILONERROR, true); - - curl_setopt($resource, CURLOPT_HTTPHEADER, $this->parseHeaderMat()); - curl_setopt($resource, CURLOPT_SSL_FALSESTART, true); - curl_setopt($resource, CURLOPT_FORBID_REUSE, false); - curl_setopt($resource, CURLOPT_FRESH_CONNECT, false); - - if (!empty($this->agent)) { - curl_setopt($resource, CURLOPT_USERAGENT, $this->agent); - } - - curl_setopt($resource, CURLOPT_NOBODY, FALSE); - curl_setopt($resource, CURLOPT_RETURNTRANSFER, TRUE);//返回内容 - curl_setopt($resource, CURLOPT_FOLLOWLOCATION, TRUE);// 跟踪重定向 - curl_setopt($resource, CURLOPT_ENCODING, 'gzip,deflate'); - - if ($method === self::POST) { - curl_setopt($resource, CURLOPT_POST, 1); - } - - curl_setopt($resource, CURLOPT_URL, $path); - curl_setopt($resource, CURLOPT_CUSTOMREQUEST, $method); - - return $resource; - } - - - /** - * @param $path - * @param $params - * @return string - */ - private function joinGetParams($path, $params) - { - if (empty($params)) { - return $path; - } - if (!is_string($params)) { - $params = http_build_query($params); - } - if (strpos($path, '?') !== false) { - [$path, $getParams] = explode('?', $path); - } - if (!isset($getParams) || empty($getParams)) { - return $path . '?' . $params; - } - return $path . '?' . $params . '&' . $getParams; - } - - - /** - * @param $curl - * @return bool|string - * @throws Exception - */ - private function execute($curl) - { - $output = curl_exec($curl); - if ($output === false) { - return new Result(['code' => 400, 'message' => curl_error($curl)]); - } - if (!$this->isUseKeepAlive()) { - curl_close($curl); - } - return $this->parseResponse($output); - } - - - /** - * @param $output - * @param $params - * @return array|Result|mixed - * @throws Exception - */ - private function parseResponse($output, $params = []) - { - if ($output === FALSE) { - return new Result(['code' => 500, 'message' => $output]); - } - [$header, $body, $status] = $this->explode($output); - if ($status != 200 && $status != 201) { - $data = new Result(['code' => $status, 'message' => $body, 'header' => $header]); - } else { - $data = $this->structure($body, $params, $header); - } - return $data; - } - - - /** - * @param $body - * @param $_data - * @param $header - * @param $statusCode - * @return array|mixed|Result - * 构建返回体 - */ - private function structure($body, $_data, $header = [], $statusCode = 200) - { - if ($this->callback instanceof Closure) { - $result = call_user_func($this->callback, $body, $_data, $header); - } else { - $result = $this->parseResult($body, $header, $statusCode); - } - return $result; - } - - - /** - * @param $body - * @param $header - * @param $statusCode - * @return Result - */ - private function parseResult($body, $header, $statusCode) - { - if (is_string($body)) { - $result['code'] = 0; - $result['message'] = ''; - } else { - $result['code'] = $body[$this->errorCodeField] ?? 0; - $result['message'] = $this->searchMessageByData($body); - } - $result['data'] = $body; - $result['header'] = $header; - $result['httpStatus'] = $statusCode; - return new Result($result); - } - - - /** - * @param $body - * @return array|mixed|string - */ - private function searchMessageByData($body) - { - $parent = []; - if (empty($this->errorMsgField)) { - return 'Unknown service status.'; - } - $explode = explode('.', $this->errorMsgField); - if (!isset($body[$explode[0]])) { - return 'Unknown service status.'; - } - foreach ($explode as $item) { - if (empty($item)) { - continue; - } - if (empty($parent)) { - $parent = $body[$item]; - continue; - } - if (is_string($parent) || !isset($parent[$item])) { - break; - } - $parent = $parent[$item]; - } - return !empty($parent) ? $parent : 'Unknown service status.'; - } - - - /** - * @param $output - * @return array - * @throws Exception - */ - private function explode($output) - { - if (empty($output) || strpos($output, "\r\n\r\n") === false) { - throw new Exception('Get data null.'); - } - - [$header, $body] = explode("\r\n\r\n", $output, 2); - if ($header == 'HTTP/1.1 100 Continue') { - [$header, $body] = explode("\r\n\r\n", $body, 2); - } - - $header = explode("\r\n", $header); - - $status = (int)explode(' ', trim($header[0]))[1]; - $header = $this->headerFormat($header); - - return [$header, $this->resolve($header, $body), $status]; - } - - /** - * @param $headers - * @return array - */ - private function headerFormat($headers) - { - $_tmp = []; - foreach ($headers as $key => $val) { - $trim = explode(': ', trim($val)); - - $_tmp[strtolower($trim[0])] = $trim[1] ?? ''; - } - return $_tmp; - } - - - /** - * @param $data - * @param $body - * @return mixed - */ - private function resolve($data, $body) - { - if (is_array($body) || !empty($this->callback)) { - return $body; - } - $type = $data['content-type'] ?? $data['Content-Type'] ?? 'text/html'; - if (strpos($type, 'json') !== false) { - return json_decode($body, true); - } else if (strpos($type, 'xml') !== false) { - return Help::xmlToArray($body); - } else if (strpos($type, 'plain') !== false) { - return Help::toArray($body); - } - return $body; - } - - - /** - * @return array - */ - private function parseHeaderMat() - { - $headers = []; - foreach ($this->headers as $key => $val) { - $headers[$key] = $key . ': ' . $val; - } - return array_values($headers); - } - - - /** - * @param string $string - * @return array|string[] - */ - private function matchHost(string $string) - { - if (($parse = isUrl($string, true)) === false) { - return $this->defaultString($string); - } - [$isHttps, $domain, $port, $path] = $parse; - if (strpos($domain, ':' . $port) !== false) { - $domain = str_replace(':' . $port, '', $domain); - } - if (isIp($domain)) { - $this->host = $domain; - } else { - $this->host = System::gethostbyname($domain) ?? $domain; - } - - if (!empty($this->port)) { - $port = $this->port; - } - if (!empty($port) && $port != 443) { - $this->host .= ':' . $port; - } - - $this->headers['Host'] = $domain; - if (strpos($path, '/') !== 0) { - $path = '/' . $path; - } - return [$this->host, $isHttps, $path]; - } - - - /** - * @param $string - * @return array - */ - private function defaultString($string) - { - $host = $this->getHost(); - if (!empty($this->port) && $this->port != 443) { - $host .= ':' . $this->getPort(); - } - if ($string == '/') { - $string = ''; - } else if (strpos($string, '/') !== 0) { - $string = '/' . $string; - } - return [$host, false, $string]; - } - + /** + * @param $path + * @param $method + * @param array $params + * @return bool|string + * @throws Exception + */ + public function request($method, $path, $params = []) + { + if ($method == self::GET) { + $path = $this->joinGetParams($path, $params); + } + return $this->execute($this->getCurlHandler($path, $method, $params)); + } + + + /** + * @param $path + * @param $method + * @param $params + * @return mixed|resource + * @throws Exception + */ + private function getCurlHandler($path, $method, $params) + { + [$host, $isHttps, $path] = $this->matchHost($path); + + $resource = $this->do(curl_init($host . $path), $host . $path, $method); + if ($method === self::POST && !empty($params)) { + curl_setopt($resource, CURLOPT_POSTFIELDS, HttpParse::parse($params)); + } + + if ($isHttps !== false) { + return $this->curlHandlerSslSet($resource); + } + return $resource; + } + + + /** + * @param $path + * @param $params + * @return mixed|resource + * @throws Exception + */ + public function upload($path, $params = []) + { + [$host, $isHttps, $path] = $this->matchHost($path); + $resource = $this->do(curl_init($host . $path), $host . $path, self::POST); + + @curl_setopt($resource, CURLOPT_POSTFIELDS, $params); + + if ($isHttps !== false) { + return $this->execute($this->curlHandlerSslSet($resource)); + } + return $this->execute($resource); + } + + + /** + * @param $resource + * @return bool + * @throws Exception + */ + private function curlHandlerSslSet($resource) + { + if (!empty($this->ssl_key)) { + if (!file_exists($this->ssl_key)) { + throw new Exception('SSL protocol certificate not found.'); + } + curl_setopt($resource, CURLOPT_SSLKEY, $this->getSslKeyFile()); + } + if (!empty($this->ssl_cert)) { + if (!!file_exists($this->ssl_cert)) { + throw new Exception('SSL protocol certificate not found.'); + } + curl_setopt($resource, CURLOPT_SSLCERT, $this->getSslCertFile()); + } + return $resource; + } + + + /** + * @param $resource + * @param $path + * @param $method + * @return resource + * @throws Exception + */ + private function do($resource, $path, $method) + { + curl_setopt($resource, CURLOPT_TIMEOUT, $this->getTimeout()); // 超时设置 + curl_setopt($resource, CURLOPT_CONNECTTIMEOUT, $this->getConnectTimeout()); // 超时设置 + + curl_setopt($resource, CURLOPT_HEADER, true); + curl_setopt($resource, CURLOPT_FAILONERROR, true); + + curl_setopt($resource, CURLOPT_HTTPHEADER, $this->parseHeaderMat()); + curl_setopt($resource, CURLOPT_SSL_FALSESTART, true); + curl_setopt($resource, CURLOPT_FORBID_REUSE, false); + curl_setopt($resource, CURLOPT_FRESH_CONNECT, false); + + if (!empty($this->getAgent())) { + curl_setopt($resource, CURLOPT_USERAGENT, $this->getAgent()); + } + + curl_setopt($resource, CURLOPT_NOBODY, FALSE); + curl_setopt($resource, CURLOPT_RETURNTRANSFER, TRUE);//返回内容 + curl_setopt($resource, CURLOPT_FOLLOWLOCATION, TRUE);// 跟踪重定向 + curl_setopt($resource, CURLOPT_ENCODING, 'gzip,deflate'); + + if ($method === self::POST) { + curl_setopt($resource, CURLOPT_POST, 1); + } + + curl_setopt($resource, CURLOPT_URL, $path); + curl_setopt($resource, CURLOPT_CUSTOMREQUEST, $method); + + return $resource; + } + + + /** + * @param $curl + * @return bool|string + * @throws Exception + */ + private function execute($curl) + { + $output = curl_exec($curl); + if ($output === false) { + return $this->fail(400, curl_error($curl)); + } + return $this->parseResponse($curl, $output); + } + + + /** + * @param $curl + * @param $output + * @param array $params + * @return array|Result|mixed + * @throws Exception + */ + private function parseResponse($curl, $output, $params = []) + { + curl_close($curl); + if ($output === FALSE) { + return $this->fail(500, $output); + } + [$header, $body, $status] = $this->explode($output); + if ($status != 200 && $status != 201) { + $data = $this->fail($status, $body, [], $header); + } else { + $data = $this->structure($body, $params, $header); + } + return $data; + } + + + /** + * @param $output + * @return array + * @throws Exception + */ + private function explode($output) + { + if (empty($output) || strpos($output, "\r\n\r\n") === false) { + throw new Exception('Get data null.'); + } + + [$header, $body] = explode("\r\n\r\n", $output, 2); + if ($header == 'HTTP/1.1 100 Continue') { + [$header, $body] = explode("\r\n\r\n", $body, 2); + } + + $header = explode("\r\n", $header); + + $status = (int)explode(' ', trim($header[0]))[1]; + $header = $this->headerFormat($header); + + return [$header, $this->resolve($header, $body), $status]; + } + + /** + * @param $headers + * @return array + */ + private function headerFormat($headers) + { + $_tmp = []; + foreach ($headers as $key => $val) { + $trim = explode(': ', trim($val)); + + $_tmp[strtolower($trim[0])] = $trim[1] ?? ''; + } + return $_tmp; + } + + + /** + * @return array + */ + private function parseHeaderMat() + { + $headers = []; + foreach ($this->getHeader() as $key => $val) { + $headers[$key] = $key . ': ' . $val; + } + return array_values($headers); + } } diff --git a/HttpServer/Client/Http2.php b/HttpServer/Client/Http2.php index 114afd94..90e9999b 100644 --- a/HttpServer/Client/Http2.php +++ b/HttpServer/Client/Http2.php @@ -10,7 +10,7 @@ use Snowflake\Abstracts\Component; use Snowflake\Core\Help; use Snowflake\Core\JSON; use Swoole\Http2\Request; -use \Swoole\Coroutine\Http2\Client as H2Client; +use Swoole\Coroutine\Http2\Client as H2Client; /** diff --git a/HttpServer/Client/IClient.php b/HttpServer/Client/IClient.php new file mode 100644 index 00000000..8864796a --- /dev/null +++ b/HttpServer/Client/IClient.php @@ -0,0 +1,75 @@ +