From 3fc18c71a20aa0c633b425ed89b8addda879672d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mr=C2=B7x?= Date: Wed, 29 Sep 2021 16:31:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 25 ++ src/Client.php | 141 +++++++++ src/ClientAbstracts.php | 671 ++++++++++++++++++++++++++++++++++++++++ src/Curl.php | 201 ++++++++++++ src/HttpParse.php | 109 +++++++ src/IClient.php | 191 ++++++++++++ 6 files changed, 1338 insertions(+) create mode 100644 composer.json create mode 100644 src/Client.php create mode 100644 src/ClientAbstracts.php create mode 100644 src/Curl.php create mode 100644 src/HttpParse.php create mode 100644 src/IClient.php diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..d639ea7 --- /dev/null +++ b/composer.json @@ -0,0 +1,25 @@ +{ + "name": "game-worker/kiri-client", + "description": "db", + "authors": [ + { + "name": "XiangLin", + "email": "as2252258@163.com" + } + ], + "license": "MIT", + "require": { + "php": ">=8.0", + "ext-json": "*", + "ext-redis": "*", + "psr/http-client": "^1.0", + "psr/http-message": "^1.0" + }, + "autoload": { + "psr-4": { + "Http\\Handler\\Client\\": "src/" + } + }, + "require-dev": { + } +} diff --git a/src/Client.php b/src/Client.php new file mode 100644 index 0000000..cd0f2f0 --- /dev/null +++ b/src/Client.php @@ -0,0 +1,141 @@ +withMethod($method) + ->coroutine( + $this->matchHost($path), + $this->paramEncode($params) + ); + } + + + /** + * @param $url + * @param array|string $data + * @return ResponseInterface + * @throws Exception 使用swoole协程方式请求 + */ + private function coroutine($url, array|string $data = []): ResponseInterface + { + try { + $client = $this->generate_client($data, ...$url); + if ($client->statusCode < 0) { + throw new Exception($client->errMsg); + } + return (new Response())->withStatus($client->getStatusCode()) + ->withHeaders($client->getHeaders()) + ->withBody(new Stream($client->getBody())); + } catch (\Throwable $exception) { + $this->addError($exception, 'rpc'); + return (new Response())->withStatus(-1)->withHeaders([]) + ->withBody(new Stream(jTraceEx($exception))); + } + } + + + /** + * @param $data + * @param $host + * @param $isHttps + * @param $path + * @return SwowClient + */ + private function generate_client($data, $host, $isHttps, $path): SwowClient + { + if ($isHttps || $this->isSSL()) { + $client = new SwowClient($host, 443, true); + } else { + $client = new SwowClient($host, $this->getPort(), false); + } + $client->set($this->settings()); + if (!empty($this->getAgent())) { + $this->withAddedHeader('User-Agent', $this->getAgent()); + } + $client->setHeaders($this->getHeader()); + $client->setMethod(strtoupper($this->getMethod())); + $client->execute($this->setParams($client, $path, $data)); + $client->close(); + return $client; + } + + + /** + * @param SwowClient $client + * @param $path + * @param $data + * @return string + */ + private function setParams(SwowClient $client, $path, $data): string + { + $content = $this->getData()->getContents(); + if (!empty($content)) { + $client->setData($content); + } + if ($this->isGet()) { + if (!empty($data)) $path .= '?' . $data; + } else { + $data = $this->mergeParams($data); + if (!empty($data)) { + $client->setData($data); + } + } + return $path; + } + + /** + * @return array + */ + #[Pure] private function settings(): array + { + $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/src/ClientAbstracts.php b/src/ClientAbstracts.php new file mode 100644 index 0000000..12f20b6 --- /dev/null +++ b/src/ClientAbstracts.php @@ -0,0 +1,671 @@ +request(self::POST, $path, $params); + } + + + /** + * @param string $path + * @param array $params + * @return ResponseInterface + */ + public function put(string $path, array $params = []): ResponseInterface + { + return $this->request(self::PUT, $path, $params); + } + + + /** + * @param string $contentType + * @return ClientAbstracts + */ + public function withContentType(string $contentType): static + { + $this->header['Content-Type'] = $contentType; + return $this; + } + + + /** + * @param string $path + * @param array $params + * @return ResponseInterface + */ + public function head(string $path, array $params = []): ResponseInterface + { + return $this->request(self::HEAD, $path, $params); + } + + + /** + * @param string $path + * @param array $params + * @return ResponseInterface + */ + public function get(string $path, array $params = []): ResponseInterface + { + return $this->request(self::GET, $path, $params); + } + + /** + * @param string $path + * @param array $params + * @return ResponseInterface + */ + public function option(string $path, array $params = []): ResponseInterface + { + return $this->request(self::OPTIONS, $path, $params); + } + + /** + * @param string $path + * @param array $params + * @return ResponseInterface + */ + public function delete(string $path, array $params = []): ResponseInterface + { + return $this->request(self::DELETE, $path, $params); + } + + /** + * @param string $path + * @param array $params + * @return ResponseInterface + */ + public function options(string $path, array $params = []): ResponseInterface + { + return $this->request(self::OPTIONS, $path, $params); + + } + + /** + * @param string $path + * @param array $params + * @return ResponseInterface + */ + public function upload(string $path, array $params = []): ResponseInterface + { + return $this->request(self::UPLOAD, $path, $params); + } + + + /** + * @return string + */ + public function getHost(): string + { + return $this->host; + } + + /** + * @return int + */ + #[Pure] protected function getHostPort(): int + { + if (!empty($this->getPort())) { + return $this->getPort(); + } + $port = 80; + if ($this->isSSL()) $port = 443; + return $port; + } + + + /** + * @param string $host + * @return ClientAbstracts + */ + public function withHost(string $host): static + { + $this->host = $host; + if (Context::inCoroutine()) { + $this->host = System::gethostbyname($host); + } + return $this->withAddedHeader('Host', $host); + } + + /** + * @return array + */ + public function getHeader(): array + { + return $this->header; + } + + /** + * @param array $header + * @return ClientAbstracts + */ + public function withHeader(array $header): static + { + $this->header = $header; + return $this; + } + + + /** + * @param array $header + * @return ClientAbstracts + */ + public function withHeaders(array $header): static + { + if (empty($header)) { + return $this; + } + foreach ($header as $key => $val) { + $this->header[$key] = $val; + } + return $this; + } + + /** + * @param $key + * @param $value + * @return ClientAbstracts + */ + public function withAddedHeader($key, $value): static + { + $this->header[$key] = $value; + return $this; + } + + /** + * @return int + */ + public function getTimeout(): int + { + return $this->timeout; + } + + /** + * @param int $value + * @return ClientAbstracts + */ + public function withTimeout(int $value): static + { + $this->timeout = $value; + return $this; + } + + + /** + * @param Closure|null $value + * @return ClientAbstracts + */ + public function withCallback(?Closure $value): static + { + return $this; + } + + /** + * @return string + */ + public function getMethod(): string + { + return $this->method; + } + + /** + * @param string $value + * @return static + */ + public function withMethod(string $value): static + { + $this->method = $value; + return $this; + } + + /** + * @return bool + */ + public function isSSL(): bool + { + return $this->isSSL; + } + + /** + * @param bool $isSSL + * @return ClientAbstracts + */ + public function withIsSSL(bool $isSSL): static + { + $this->isSSL = $isSSL; + return $this; + } + + /** + * @return string + */ + public function getAgent(): string + { + return $this->agent; + } + + /** + * @param string $agent + * @return ClientAbstracts + */ + public function withAgent(string $agent): static + { + $this->agent = $agent; + return $this; + } + + + /** + * @return string + */ + public function getSslCertFile(): string + { + return $this->ssl_cert_file; + } + + /** + * @param string $ssl_cert_file + * @return ClientAbstracts + */ + public function withSslCertFile(string $ssl_cert_file): static + { + $this->ssl_cert_file = $ssl_cert_file; + return $this; + } + + /** + * @return string + */ + public function getSslKeyFile(): string + { + return $this->ssl_key_file; + } + + /** + * @param string $ssl_key_file + * @return ClientAbstracts + */ + public function withSslKeyFile(string $ssl_key_file): static + { + $this->ssl_key_file = $ssl_key_file; + return $this; + } + + /** + * @return string + */ + public function getCa(): string + { + return $this->ca; + } + + /** + * @param string $ssl_key_file + * @return static + */ + public function withCa(string $ssl_key_file): static + { + $this->ca = $ssl_key_file; + return $this; + } + + /** + * @return int + */ + #[Pure] public function getPort(): int + { + if ($this->isSSL()) { + return 443; + } + if (empty($this->port)) { + return 80; + } + return $this->port; + } + + /** + * @param int $port + * @return ClientAbstracts + */ + public function withPort(int $port): static + { + $this->port = $port; + return $this; + } + + + /** + * @return StreamInterface + */ + public function getData(): StreamInterface + { + if (!$this->_data) { + $this->_data = new Stream(); + } + return $this->_data; + } + + /** + * @param string|StreamInterface $data + * @return ClientAbstracts + */ + public function withBody(string|StreamInterface $data): static + { + if (is_string($data)) { + $data = new Stream($data); + } + $this->_data = $data; + return $this; + } + + /** + * @return int + */ + public function getConnectTimeout(): int + { + return $this->connect_timeout; + } + + /** + * @param int $connect_timeout + * @return ClientAbstracts + */ + public function withConnectTimeout(int $connect_timeout): static + { + $this->connect_timeout = $connect_timeout; + return $this; + } + + + /** + * @param $host + * @return string|string[] + */ + protected function replaceHost($host): array|string + { + 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): bool|int + { + return preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $url); + } + + /** + * @param $url + * @return bool + */ + protected function isHttp($url): bool + { + return str_starts_with($url, 'http://'); + } + + /** + * @param $url + * @return bool + */ + protected function isHttps($url): bool + { + return str_starts_with($url, 'https://'); + } + + + /** + * @param $newData + * @return string + */ + protected function mergeParams($newData): string + { + if (!is_string($newData)) { + return $this->toRequest($newData); + } + return $newData; + } + + + /** + * @param $data + * @return string + */ + protected function toRequest($data): string + { + 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 (str_contains($contentType, 'json')) { + return Help::toJson($data); + } else if (str_contains($contentType, 'xml')) { + return Help::toXml($data); + } else { + return http_build_query($data); + } + } + + + /** + * @param $data + * @param $body + * @return array|string|null + */ + protected function resolve($data, $body): array|string|null + { + if (is_array($body)) { + return $body; + } + $type = $data['content-type'] ?? $data['Content-Type'] ?? 'text/html'; + if (str_contains($type, 'text/html')) { + return $body; + } else if (str_contains($type, 'json')) { + return json_decode($body, true); + } else if (str_contains($type, 'xml')) { + return Help::xmlToArray($body); + } else if (str_contains($type, 'plain')) { + return Help::toArray($body); + } + return $body; + } + + + /** + * @return bool + * check isPost Request + */ + #[Pure] public function isPost(): bool + { + return strtolower($this->method) === self::POST; + } + + /** + * @return bool + * check isPost Request + */ + #[Pure] public function isUpload(): bool + { + return strtolower($this->method) === self::UPLOAD; + } + + + /** + * @return bool + * + * check isGet Request + */ + #[Pure] public function isGet(): bool + { + return strtolower($this->method) === self::GET; + } + + /** + * @param $arr + * + * @return array|string + * 将请求参数进行编码 + */ + #[Pure] protected function paramEncode($arr): array|string + { + 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 + */ + protected function matchHost(string $string): array + { + if (($parse = isUrl($string, true)) === false) { + return $this->defaultString($string); + } + [$isHttps, $domain, $port, $path] = $parse; + if (str_contains($domain, ':' . $port)) { + $domain = str_replace(':' . $port, '', $domain); + } + $this->port = $isHttps ? 443 : $this->port; + if (isIp($domain)) { + $this->host = $domain; + } else if (Context::inCoroutine()) { + $this->host = System::gethostbyname($domain) ?? $domain; + } else { + $this->host = $domain; + } + $this->header['Host'] = $domain; + if (!str_starts_with($path, '/')) { + $path = '/' . $path; + } + return [$this->host, $isHttps, $path]; + } + + + /** + * @param $string + * @return array + */ + private function defaultString($string): array + { + $host = $this->getHost(); + if ($string == '/') { + $string = '/'; + } else if (!str_starts_with($string, '/')) { + $string = '/' . $string; + } + return [$host, $this->isSSL(), $string]; + } + + + /** + * @param $path + * @param $params + * @return string + */ + protected function joinGetParams($path, $params): string + { + if (empty($params)) { + return $path; + } + if (!is_string($params)) { + $params = http_build_query($params); + } + if (str_contains($path, '?')) { + [$path, $getParams] = explode('?', $path); + } + if (empty($getParams)) { + return $path . '?' . $params; + } + return $path . '?' . $params . '&' . $getParams; + } + +} diff --git a/src/Curl.php b/src/Curl.php new file mode 100644 index 0000000..854fc22 --- /dev/null +++ b/src/Curl.php @@ -0,0 +1,201 @@ +joinGetParams($path, $params); + } + return $this->execute($this->getCurlHandler($path, $method, $params)); + } + + + /** + * @param $path + * @param $method + * @param $params + * @return CurlHandle + * @throws Exception + */ + private function getCurlHandler($path, $method, $params): CurlHandle + { + [$host, $isHttps, $path] = $this->matchHost($path); + + $host = $isHttps ? 'https://' . $host : 'http://' . $host; + if ($this->getPort() != 443 && $this->getPort() != 80) { + $host .= ':' . $this->getPort(); + } + + $resource = $this->do(curl_init($host . $path), $host . $path, $method); + if ($isHttps !== false) { + $this->curlHandlerSslSet($resource); + } + + $contents = $this->getData()->getContents(); + if (empty($params) && empty($contents)) { + return $resource; + } + + if (!empty($contents)) { + curl_setopt($resource, CURLOPT_POSTFIELDS, $contents); + } else if ($method === self::POST) { + curl_setopt($resource, CURLOPT_POSTFIELDS, $this->mergeParams($params)); + } else if ($method === self::UPLOAD) { + curl_setopt($resource, CURLOPT_POSTFIELDS, $params); + } + return $resource; + } + + + /** + * @param $resource + * @return void + * @throws Exception + */ + private function curlHandlerSslSet($resource): void + { + 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()); + } + } + + + /** + * @param $resource + * @param $path + * @param $method + * @return CurlHandle + * @throws Exception + */ + private function do($resource, $path, $method): CurlHandle + { + curl_setopt($resource, CURLOPT_URL, $path); + 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()); + if (defined('CURLOPT_SSL_FALSESTART')) { + 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 || $method == self::UPLOAD) { + curl_setopt($resource, CURLOPT_POST, 1); + } + curl_setopt($resource, CURLOPT_CUSTOMREQUEST, strtoupper($method)); + + return $resource; + } + + + /** + * @param $curl + * @return ResponseInterface + * @throws Exception + */ + private function execute($curl): ResponseInterface + { + $output = curl_exec($curl); + curl_close($curl); + if ($output === false) { + $response = (new Response())->withStatus(400)->withBody(new Stream(curl_error($curl))); + } else { + $response = $this->explode($output); + } + return $response; + } + + + /** + * @param $output + * @return ResponseInterface + * @throws Exception + */ + private function explode($output): ResponseInterface + { + [$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 = explode(' ', array_shift($header)); + + return (new Response())->withStatus(intval($status[1]))->withHeaders($this->headerFormat($header)) + ->withBody(new Stream($body)); + } + + /** + * @param $headers + * @return array + */ + private function headerFormat($headers): array + { + $_tmp = []; + foreach ($headers as $val) { + $trim = explode(': ', trim($val)); + + $_tmp[strtolower($trim[0])] = [$trim[1] ?? '']; + } + return $_tmp; + } + + + /** + * @return array + */ + #[Pure] private function parseHeaderMat(): array + { + $headers = []; + foreach ($this->getHeader() as $key => $val) { + $headers[$key] = $key . ': ' . $val; + } + return array_values($headers); + } +} diff --git a/src/HttpParse.php b/src/HttpParse.php new file mode 100644 index 0000000..a7201af --- /dev/null +++ b/src/HttpParse.php @@ -0,0 +1,109 @@ + $value) { + if ($value === null) { + continue; + } + if (is_array($value)) { + $value = key($value); + } + if ($first === '') { + $first = $value; + } else { + $tp[] = $value; + } + } + $key = $first . '[' . implode('][', $tp) . ']'; + if (count($tp) < 1) { + $key = $first; + } + return $key; + } + + /** + * @param $data + * @return string + * @throws Exception + */ + public static function parse($data): string + { + $tmp = []; + if (is_string($data)) { + return $data; + } + foreach ($data as $key => $datum) { + if ($datum === null) { + continue; + } + $tmp[] = static::ifElse($key, $datum); + } + return implode('&', $tmp); + } + + /** + * @param $t + * @param $qt + * @return string + * @throws Exception + */ + private static function ifElse($t, $qt): string + { + if (is_numeric($qt)) { + return $t . '=' . $qt; + } + if (is_string($qt)) { + $string = $t . '=' . urlencode($qt); + } else { + $string = static::encode($t, $qt); + } + return $string; + } + + /** + * @param mixed ...$object + * @return string + * @throws Exception + */ + private static function encode(...$object): string + { + $ret = []; + + $data = $object[count($object) - 1]; + $key = static::getKey(...$object); + foreach ($data as $s => $datum) { + if (is_array($datum)) { + $object[count($object) - 1] = $s; + $object[] = $datum; + $string = static::encode(...$object); + } else { + if (is_object($datum)) { + throw new Exception('Http body con\'t object.'); + } + $string = $key . '=' . urlencode($datum); + } + $ret[] = $string; + } + return implode('&', $ret); + } + + +} diff --git a/src/IClient.php b/src/IClient.php new file mode 100644 index 0000000..9ace49c --- /dev/null +++ b/src/IClient.php @@ -0,0 +1,191 @@ +