diff --git a/error.php b/error.php new file mode 100644 index 00000000..22330053 --- /dev/null +++ b/error.php @@ -0,0 +1,31 @@ + 'ok', + NO_AUTH => '' +]); + +if (!function_exists('message')) { + + /** + * @param $code + * @param $replace + * @param string $default + * @return mixed|string + */ + function message($code, $replace, $default = '') + { + if (!isset(ERROR_MESSAGES[$code])) { + if (!empty($default)) { + return $default; + } + return 'unknown error'; + } + return sprintf(ERROR_MESSAGES[$code], $replace); + } + + +} diff --git a/function.php b/function.php new file mode 100644 index 00000000..c1aebbe2 --- /dev/null +++ b/function.php @@ -0,0 +1,145 @@ +$name; + } else if (Snowflake::has($default)) { + $class = Snowflake::get()->$default; + } else { + $class = Snowflake::createObject($default); + Snowflake::setAlias($name, $default); + } + return $class; + } + + +} + + +if (!function_exists('storage')) { + + /** + * @param string $fileName + * @param string $path + * @return string + * @throws Exception + */ + function storage($fileName = '', $path = '') + { + $basePath = Snowflake::getStoragePath(); +// if (empty($path)) { +// return $basePath . '/' . $fileName; +// } else if (empty($fileName)) { +// return initDir($basePath, $path); +// } + return initDir($basePath, $path) . $fileName; + } + + + /** + * @param $basePath + * @param $path + * @return false|string + * @throws Exception + */ + function initDir($basePath, $path) + { + $explode = array_filter(explode('/', $path)); + foreach ($explode as $value) { + $path .= $value . '/'; + if (!is_dir($basePath . $path)) { +// mkdir($basePath . $path); + } + if (!is_dir($basePath . $path)) { +// throw new Exception('System error, directory ' . $basePath . $path . ' is not writable'); + } + } + return realpath($basePath . $path); + } + + +} + + +if (!function_exists('alias')) { + + /** + * @param $class + * @param $name + */ + function alias($class, $name) + { + Snowflake::setAlias($class, $name); + } + +} + + +if (!function_exists('name')) { + + function name($name) + { + swoole_set_process_name($name); + } + +} + +if (!function_exists('response')) { + + /** + * @return Response|stdClass + * @throws + */ + function response() + { + if (!Snowflake::has('response')) { + return make('response', Response::class); + } + return Snowflake::get()->response; + } + +} + +if (!function_exists('redirect')) { + + function redirect($url) + { + return response()->redirect($url); + } + +} + + + +if (!function_exists('env')) { + + /** + * @param $key + * @param null $default + * @return array|false|string|null + */ + function env($key, $default = null) + { + $env = getenv($key); + if ($env === false) { + return $default; + } + return $env; + } + +} diff --git a/http-server/Abstracts/BaseContext.php b/http-server/Abstracts/BaseContext.php new file mode 100644 index 00000000..c1f51642 --- /dev/null +++ b/http-server/Abstracts/BaseContext.php @@ -0,0 +1,49 @@ + 0) + { + self::$pool[$cid][$key] = $item; + } + + } + + static function delete($key = null) + { + $cid = Coroutine::getuid(); + if ($cid > 0) + { + if($key){ + unset(self::$pool[$cid][$key]); + }else{ + unset(self::$pool[$cid]); + } + } + } +} diff --git a/http-server/Abstracts/HttpService.php b/http-server/Abstracts/HttpService.php new file mode 100644 index 00000000..4d117340 --- /dev/null +++ b/http-server/Abstracts/HttpService.php @@ -0,0 +1,14 @@ +server; + } + + /** + * @param $server + */ + public function setServer($server) + { + $this->server = $server; + } + +} diff --git a/http-server/Application.php b/http-server/Application.php new file mode 100644 index 00000000..7ec41a2c --- /dev/null +++ b/http-server/Application.php @@ -0,0 +1,51 @@ +logger; + $logger->write($message, $category); + $logger->insert(); + } + + /** + * @param $methods + * @return mixed + * @throws Exception + */ + public function __get($methods) + { + if (method_exists($this, $methods)) { + return $this->{$methods}(); + } + $handler = 'get' . ucfirst($methods); + if (method_exists($this, $handler)) { + return $this->{$handler}(); + } + if (property_exists($this, $methods)) { + return $this->$methods; + } + $message = sprintf('method %s::%s not exists.', get_called_class(), $methods); + throw new Exception($message); + } + +} diff --git a/http-server/Client/Client.php b/http-server/Client/Client.php new file mode 100644 index 00000000..10fe3b31 --- /dev/null +++ b/http-server/Client/Client.php @@ -0,0 +1,1014 @@ +ca; + } + + /** + * @param string $ca + */ + public function setCa(string $ca): void + { + $this->ca = $ca; + } + + + /** + * @return string + */ + public function getPort(): string + { + return $this->port; + } + + /** + * @param string $port + */ + public function setPort(string $port): void + { + $this->port = $port; + } + + const POST = 'post'; + const GET = 'get'; + const PUT = 'put'; + const DELETE = 'delete'; + const OPTIONS = 'option'; + + /** + * HttpClient constructor. + */ + private function __construct() + { + } + + /** + * @param $data + */ + public function setData($data) + { + $this->_data = $data; + } + + /** + * @return string + */ + public function getSslCertFile(): string + { + return $this->ssl_cert_file; + } + + /** + * @return string + */ + public function hasSslCertFile(): string + { + return !empty($this->ssl_cert_file) && file_exists($this->ssl_cert_file); + } + + /** + * @return string + */ + public function hasSslKeyFile(): string + { + 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 \Swoole\Coroutine\Client($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)) { + return new Result(['code' => 500, 'message' => $client->errMsg]); + } + + $path = '/' . $this->port . '/' . ltrim($path, '/'); + + $params['body'] = $data; + $params['path'] = $path; + $params['header']['request_uri'] = $path; + $params['header']['request_method'] = 'receive'; + + if ($client->send(serialize($params))) { + $recv = $this->timeout > 0 ? $client->recv($this->timeout) : $client->recv(); + $param = ['code' => 0, 'message' => Help::toArray($recv)]; + } else { + $param = ['code' => 500, 'message' => $client->errMsg]; + } + $client->close(); + return new Result($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->timeout > 0) { + $params['timeout'] = $this->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; + } +} diff --git a/http-server/Client/Result.php b/http-server/Client/Result.php new file mode 100644 index 00000000..10cea0df --- /dev/null +++ b/http-server/Client/Result.php @@ -0,0 +1,187 @@ + $val) { + $this->$key = $val; + } + } + + /** + * @param $name + * @return mixed|null + */ + public function __get($name) + { + return $this->$name; + } + + /** + * @param $name + * @param $value + * @return $this|void + */ + public function __set($name, $value) + { + $this->$name = $value; + + return $this; + } + + /** + * @return array + */ + public function getHeaders() + { + $_tmp = []; + if (!is_array($this->header)) { + return $_tmp; + } + foreach ($this->header as $key => $val) { + if ($key == 0) { + $_tmp['pro'] = $val; + } else { + $trim = explode(': ', $val); + + $_tmp[strtolower($trim[0])] = $trim[1]; + } + } + return $_tmp; + } + + + /** + * @return array + */ + public function getTime() + { + return [ + 'startTime' => $this->startTime, + 'requestTime' => $this->requestTime, + 'runTime' => $this->runTime, + ]; + } + + /** + * @param $key + * @param $data + * @return $this + * @throws Exception + */ + public function setAttr($key, $data) + { + if (!property_exists($this, $key)) { + throw new Exception('未查找到相应对象属性'); + } + $this->$key = $data; + return $this; + } + + /** + * @param int $status + * @return bool + */ + public function isResultsOK($status = 0) + { + if (!$this->httpIsOk()) { + return false; + } + return $this->code === $status; + } + + /** + * @return bool + */ + public function httpIsOk() + { + return in_array($this->httpStatus, $this->statusCode); + } + + /** + * @return mixed + */ + public function getResponse() + { + $headers = $this->getHeaders(); + if (!isset($headers['content-type'])) { + return $this->data; + } + if (!is_string($this->data)) { + return $this->data; + } + switch (trim($headers['content-type'])) { + case 'application/json; encoding=utf-8'; + case 'application/json;'; + case 'application/json'; + case 'text/plain'; + return json_decode($this->data, true); + break; + } + return $this->data; + } + + /** + * @param $key + * @param $data + * @return $this + */ + public function append($key, $data) + { + $this->data[$key] = $data; + return $this; + } + + /** + * @return mixed + */ + public function getMessage() + { + return $this->message; + } + + /** + * @return mixed + */ + public function getCode() + { + return $this->code; + } +} diff --git a/http-server/Controller.php b/http-server/Controller.php new file mode 100644 index 00000000..a11ec809 --- /dev/null +++ b/http-server/Controller.php @@ -0,0 +1,105 @@ +input = $input; + } + + /** + * @param HttpHeaders $headers + */ + public function setHeaders(HttpHeaders $headers): void + { + $this->headers = $headers; + } + + /** + * @param Request $request + */ + public function setRequest(Request $request): void + { + $this->request = $request; + } + + /** + * @return HttpParams + * @throws Exception + */ + public function getInput(): HttpParams + { + if (!$this->input) { + $this->input = $this->getRequest()->params; + } + return $this->input; + } + + /** + * @return HttpHeaders + * @throws Exception + */ + public function getHeaders(): HttpHeaders + { + if (!$this->headers) { + $this->headers = $this->getRequest()->headers; + } + return $this->headers; + } + + /** + * @return Request + * @throws Exception + */ + public function getRequest(): Request + { + if (!$this->request) { + $this->request = Snowflake::get()->request; + } + return $this->request; + } + + /** + * @param $name + * @return mixed|null + * @throws Exception + */ + public function __get($name) + { + $method = 'get' . ucfirst($name); + if (method_exists($this, $method)) { + return $this->$method(); + } + return parent::__get($name); + } + +} diff --git a/http-server/Events/Callback.php b/http-server/Events/Callback.php new file mode 100644 index 00000000..01e92413 --- /dev/null +++ b/http-server/Events/Callback.php @@ -0,0 +1,94 @@ +container = $container; + } + + + + + /** + * @param $server + * @param $worker_id + * @param $message + * @throws Exception + */ + protected function clear($server, $worker_id, $message) + { + Timer::clearAll(); + $event = Snowflake::get()->event; + + $event->offName(Event::EVENT_AFTER_REQUEST); + $event->offName(Event::EVENT_BEFORE_REQUEST); + $this->eventNotify($message, $event); + + Snowflake::clearProcessId($server->worker_pid); + Logger::write($this->_MESSAGE[$message] . $worker_id); + Logger::clear(); + } + + + + const EVENT_ERROR = 'WORKER:ERROR'; + const EVENT_STOP = 'WORKER:STOP'; + const EVENT_EXIT = 'WORKER:EXIT'; + + + private $_MESSAGE = [ + self::EVENT_ERROR => 'The server error. at No.', + self::EVENT_STOP => 'The server stop. at No.', + self::EVENT_EXIT => 'The server exit. at No.', + ]; + + /** + * @param $message + * @param $event + */ + private function eventNotify($message, $event) + { + switch ($message) { + case self::EVENT_ERROR: + if (!$event->exists(Event::SERVER_WORKER_ERROR)) { + return; + } + $event->trigger(Event::SERVER_WORKER_ERROR); + break; + case self::EVENT_EXIT: + if (!$event->exists(Event::SERVER_WORKER_EXIT)) { + return; + } + $event->trigger(Event::SERVER_WORKER_EXIT); + break; + case self::EVENT_STOP: + if (!$event->exists(Event::SERVER_WORKER_STOP)) { + return; + } + $event->trigger(Event::SERVER_WORKER_STOP); + break; + } + } + +} diff --git a/http-server/Events/Http.php b/http-server/Events/Http.php new file mode 100644 index 00000000..7447041f --- /dev/null +++ b/http-server/Events/Http.php @@ -0,0 +1,118 @@ +application = $application; + } + + + /** + * @param array $settings + * @param array $events + * @param array $config + * @return mixed|void + * @throws NotFindClassException + * @throws ReflectionException + */ + public function set(array $settings, $events = [], $config = []) + { + parent::set($settings); + ServerManager::set($this, $settings, $this->application, $events, $config); + } + + + /** + * @param Request $request + * @param Response $response + * @throws \Exception + */ + public function onHandler(Request $request, Response $response) + { + try { + [$sRequest, $sResponse] = static::setContext($request, $response); + $sResponse->send(Snowflake::get()->router->dispatch(), 200); + } catch (Error | \Throwable $exception) { + if (!isset($sResponse)) { + $response->status(200); + $response->end($exception->getMessage()); + } else { + $sResponse->send($this->format($exception), 200); + } + } finally { + $dividing_line = str_pad('', 100, '-'); + $this->application->debug($dividing_line, 'app'); + } + } + + + /** + * @param $exception + * @return false|int|mixed|string + * @throws Exception + */ + public function format($exception) + { + $errorInfo = [ + 'message' => $exception->getMessage(), + 'file' => $exception->getFile(), + 'line' => $exception->getLine() + ]; + $this->application->error(var_export($errorInfo, true)); + + $code = $exception->getCode() ?? 500; + $trance = array_slice($exception->getTrace(), 0, 10); + Snowflake::get()->logger->write(print_r($trance, true), 'exception'); + + return JSON::to($code, $errorInfo['message']); + } + + + /** + * @param $request + * @param $response + * @return array + * @throws Exception + */ + public static function setContext($request, $response): array + { + $request = Context::setContext('request', HRequest::create($request)); + $response = Context::setContext('response', HResponse::create($response)); + return [$request, $response]; + } + +} diff --git a/http-server/Events/Packet.php b/http-server/Events/Packet.php new file mode 100644 index 00000000..6f901591 --- /dev/null +++ b/http-server/Events/Packet.php @@ -0,0 +1,44 @@ +unpack($data))) { + throw new Exception('Format error.'); + } + $client[] = $this->pack($data); + return $server->sendto(...$client); + } catch (\Throwable $exception) { + $client[] = $this->pack(['message' => $exception->getMessage()]); + return $server->sendto(...$client); + } finally { + $event = Snowflake::get()->event; + $event->trigger(Event::SERVER_WORKER_STOP); + } + } + +} diff --git a/http-server/Events/Receive.php b/http-server/Events/Receive.php new file mode 100644 index 00000000..02a9fc91 --- /dev/null +++ b/http-server/Events/Receive.php @@ -0,0 +1,46 @@ +unpack($data))) { + throw new Exception('Format error.'); + } + $client[] = $this->pack($data); + return $server->send(...$client); + } catch (\Throwable $exception) { + $client[] = $this->pack(['message' => $exception->getMessage()]); + return $server->send(...$client); + } finally { + $event = Snowflake::get()->event; + $event->trigger(Event::SERVER_WORKER_STOP); + } + } + +} diff --git a/http-server/Events/Service.php b/http-server/Events/Service.php new file mode 100644 index 00000000..fc39711a --- /dev/null +++ b/http-server/Events/Service.php @@ -0,0 +1,128 @@ +application = $application; + } + + + /** + * @param array $settings + * @param array $events + * @param array $config + * @return mixed|void + * @throws NotFindClassException + * @throws ReflectionException + */ + public function set(array $settings, $events = [], $config = []) + { + parent::set($settings); + ServerManager::set($this, $settings, $this->application, $events, $config); + } + + + /** + * @param $callbacks + */ + protected function bindCallback($callbacks) + { + if (empty($callbacks) || !is_array($callbacks)) { + return; + } + foreach ($callbacks as $callback) { + $this->on($callback[0], [$this, $callback[1][1]]); + } + } + + + /** + * @param $eventName + * @return array + * @throws NotFindClassException + * @throws ReflectionException + * @throws Exception + */ + protected function createHandler($eventName) + { + $classPrefix = 'HttpServer\Events\Trigger\On' . ucfirst($eventName); + if (!class_exists($classPrefix)) { + throw new Exception('class not found.'); + } + $class = Snowflake::createObject($classPrefix, [Snowflake::get()]); + return [$class, 'onHandler']; + } + + + /** + * @param $data + * @return mixed + * @throws Exception + */ + public function pack($data) + { + $callback = $this->pack; + if (is_callable($callback, true)) { + return $callback($data); + } + return JSON::encode($data); + } + + + /** + * @param $data + * @return mixed + */ + public function unpack($data) + { + $callback = $this->unpack; + if (is_callable($callback, true)) { + return $callback($data); + } + return JSON::decode($data); + } + +} diff --git a/http-server/Events/Trigger/OnAfterReload.php b/http-server/Events/Trigger/OnAfterReload.php new file mode 100644 index 00000000..5e275ba4 --- /dev/null +++ b/http-server/Events/Trigger/OnAfterReload.php @@ -0,0 +1,18 @@ +event; + if (!$event->exists(Event::RECEIVE_CONNECTION)) { + return; + } + $event->trigger(Event::RECEIVE_CONNECTION, [$server, $fd, $reactorId]); + } + + +} diff --git a/http-server/Events/Trigger/OnFinish.php b/http-server/Events/Trigger/OnFinish.php new file mode 100644 index 00000000..6c88e6b3 --- /dev/null +++ b/http-server/Events/Trigger/OnFinish.php @@ -0,0 +1,26 @@ +write(var_export($data, true), 'Task'); + } + +} diff --git a/http-server/Events/Trigger/OnManagerStart.php b/http-server/Events/Trigger/OnManagerStart.php new file mode 100644 index 00000000..b87246fd --- /dev/null +++ b/http-server/Events/Trigger/OnManagerStart.php @@ -0,0 +1,33 @@ +debug('manager start.'); + Snowflake::setProcessId($server->manager_pid); + + $events = Snowflake::get()->event; + if ($events->exists(Event::SERVER_MANAGER_START)) { + $events->trigger(Event::SERVER_MANAGER_START, null, $server); + } + if (Snowflake::isLinux()) { + name('Server Manager.'); + } + } + +} diff --git a/http-server/Events/Trigger/OnManagerStop.php b/http-server/Events/Trigger/OnManagerStop.php new file mode 100644 index 00000000..d8c34406 --- /dev/null +++ b/http-server/Events/Trigger/OnManagerStop.php @@ -0,0 +1,41 @@ +warning('manager stop.'); + + $events = Snowflake::get()->event; + if ($events->exists(Event::SERVER_MANAGER_STOP)) { + $events->trigger(Event::SERVER_MANAGER_STOP, [$server]); + } + +// $runPath = storage(null, 'workerIds'); +// foreach (glob($runPath . '/*') as $item) { +// if (!file_exists($item)) { +// continue; +// } +// @unlink($item); +// } + } + +} diff --git a/http-server/Events/Trigger/OnPipeMessage.php b/http-server/Events/Trigger/OnPipeMessage.php new file mode 100644 index 00000000..9230c145 --- /dev/null +++ b/http-server/Events/Trigger/OnPipeMessage.php @@ -0,0 +1,17 @@ +master_pid); + + $event = Snowflake::get()->event; + if ($event->exists(Event::SERVER_EVENT_START)) { + $event->trigger(Event::SERVER_EVENT_START, null, $server); + } + } + +} diff --git a/http-server/Events/Trigger/OnTask.php b/http-server/Events/Trigger/OnTask.php new file mode 100644 index 00000000..28943c66 --- /dev/null +++ b/http-server/Events/Trigger/OnTask.php @@ -0,0 +1,140 @@ +onContinueTask(...func_get_args()); + } else { + $this->onTask(...func_get_args()); + } + } + + + /** + * @param Server $server + * @param int $task_id + * @param int $from_id + * @param string $data + * + * @return mixed|void + * @throws Exception + * 异步任务 + */ + public function onTask(Server $server, $task_id, $from_id, $data) + { + $time = microtime(TRUE); + if (empty($data)) { + return $server->finish('null data'); + } + $finish = $this->runTaskHandler($data); + if (!$finish) { + $finish = []; + } + $finish['runTime'] = [ + 'startTime' => $time, + 'runTime' => microtime(TRUE) - $time, + 'endTime' => microtime(TRUE), + ]; + $server->finish(json_encode($finish)); + } + + /** + * @param Server $server + * @param Server\Task $task + * @return mixed|void + * @throws Exception + * 异步任务 + */ + public function onContinueTask(Server $server, Server\Task $task) + { + $time = microtime(TRUE); + if (empty($task->data)) { + return $task->finish('null data'); + } + $finish = $this->runTaskHandler($task->data); + if (!$finish) { + $finish = []; + } + $finish['runTime'] = [ + 'startTime' => $time, + 'runTime' => microtime(TRUE) - $time, + 'endTime' => microtime(TRUE), + ]; + $task->finish(json_encode($finish)); + } + + /** + * @param $data + * @return array|null + * @throws Exception + */ + private function runTaskHandler($data) + { + $serialize = $this->before($data); + try { + $params = $serialize->getParams(); + if (is_object($params)) { + $params = get_object_vars($params); + } + $finish['class'] = get_class($serialize); + $finish['params'] = $params; + $finish['status'] = 'success'; + $finish['info'] = $serialize->handler(); + } catch (\Throwable $exception) { + $finish['status'] = 'error'; + $finish['info'] = $this->format($exception); + $this->error($exception, 'Task'); + } finally { + $event = Snowflake::get()->event; + $event->trigger(Event::RELEASE_ALL); + + $this->endCoroutine(); + Timer::clearAll(); + } + return $finish; + } + + /** + * @param $data + * @return ITask|null + */ + protected function before($data) + { + if (empty($serialize = unserialize($data))) { + return null; + } + if (!($serialize instanceof ITask)) { + return null; + } + return $serialize; + } + + /** + * @param $exception + * @return string + */ + private function format($exception) + { + return $exception->getMessage() . " on line " . $exception->getLine() . " at file " . $exception->getFile(); + } + +} diff --git a/http-server/Events/Trigger/OnWorkerError.php b/http-server/Events/Trigger/OnWorkerError.php new file mode 100644 index 00000000..93cb8e5e --- /dev/null +++ b/http-server/Events/Trigger/OnWorkerError.php @@ -0,0 +1,22 @@ +clear($server, $worker_id, self::EVENT_ERROR); + } + +} diff --git a/http-server/Events/Trigger/OnWorkerExit.php b/http-server/Events/Trigger/OnWorkerExit.php new file mode 100644 index 00000000..f1b0fde8 --- /dev/null +++ b/http-server/Events/Trigger/OnWorkerExit.php @@ -0,0 +1,22 @@ +clear($server, $worker_id, self::EVENT_EXIT); + } + +} diff --git a/http-server/Events/Trigger/OnWorkerStart.php b/http-server/Events/Trigger/OnWorkerStart.php new file mode 100644 index 00000000..03c9586f --- /dev/null +++ b/http-server/Events/Trigger/OnWorkerStart.php @@ -0,0 +1,75 @@ +worker_pid); + + $get_name = $this->get_process_name($server, $worker_id); + if (!empty($get_name) && !Snowflake::isMac()) { + swoole_set_process_name($get_name); + } + $this->setWorkerAction($server, $worker_id); + } + + /** + * @param $worker_id + * @param $socket + * @throws Exception + */ + private function setWorkerAction($socket, $worker_id) + { + try { + $event = Snowflake::get()->event; + if ($event->exists(Event::SERVER_WORKER_START)) { + $event->trigger(Event::SERVER_WORKER_START); + } + } catch (\Throwable $exception) { + Logger::write($exception->getMessage(), 'worker'); + } + } + + /** + * @param $socket + * @param $worker_id + * @return string + */ + private function get_process_name($socket, $worker_id) + { + $prefix = 'system:'; + if ($worker_id >= $socket->setting['worker_num']) { + return $prefix . ': Task: No.' . $worker_id; + } else { + return $prefix . ': worker: No.' . $worker_id; + } + } + + +} diff --git a/http-server/Events/Trigger/OnWorkerStop.php b/http-server/Events/Trigger/OnWorkerStop.php new file mode 100644 index 00000000..96c94901 --- /dev/null +++ b/http-server/Events/Trigger/OnWorkerStop.php @@ -0,0 +1,23 @@ +clear($server, $worker_id, self::EVENT_STOP); + } + +} diff --git a/http-server/Events/WebSocket.php b/http-server/Events/WebSocket.php new file mode 100644 index 00000000..8aab923f --- /dev/null +++ b/http-server/Events/WebSocket.php @@ -0,0 +1,182 @@ +application = $application; + parent::__construct($host, $port, $mode, $sock_type); + } + + + /** + * @param array $settings + * @param array $events + * @param $config + * @return mixed|void + * @throws \ReflectionException + * @throws NotFindClassException + */ + public function set(array $settings, $events = [], $config = []) + { + parent::set($settings); + ServerManager::set($this, $settings, $this->application, $events, $config); + } + + + /** + * @param Server $server + * @param Frame $frame + * @throws + */ + public function onMessage(Server $server, Frame $frame) + { + try { + $event = Snowflake::get()->event; + if ($event->exists(Event::SERVER_MESSAGE)) { + $event->trigger(Event::SERVER_MESSAGE, [$server, $frame]); + return; + } + if ($frame->opcode == 0x08) { + return; + } + $json = json_decode($frame->data, true); + + $manager = Snowflake::get()->annotation; + $manager->runWith($this->getName($json), [$frame->fd, $server]); + } catch (Exception $exception) { +// $this->error($exception->getMessage(), __METHOD__, __FILE__); +// $this->addError($exception->getMessage()); + } finally { + $event = Snowflake::get()->event; + $event->trigger(Event::EVENT_AFTER_REQUEST); + Logger::insert(); + } + } + + /** + * @param $json + * @return string + */ + private function getName($json) + { + return 'WEBSOCKET:MESSAGE:' . $json['route']; + } + + /** + * @param SRequest $request + * @param SResponse $response + * @return bool + * @throws Exception + */ + protected function connect($request, $response) + { + $manager = Snowflake::get()->event; + if ($manager->exists(Event::SERVER_HANDSHAKE)) { + return $manager->trigger(Event::SERVER_HANDSHAKE, [$request, $response]); + } + $response->status(502); + $response->end(); + return true; + } + + /** + * @param SRequest $request + * @param SResponse $response + * @return bool|string + * @throws Exception + */ + public function onHandshake(SRequest $request, SResponse $response) + { + /** @var Server $server */ + $secWebSocketKey = $request->header['sec-websocket-key']; + $patten = '#^[+/0-9A-Za-z]{21}[AQgw]==$#'; + if (0 === preg_match($patten, $secWebSocketKey) || 16 !== strlen(base64_decode($secWebSocketKey))) { + return false; + } + $key = base64_encode(sha1( + $request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', + TRUE + )); + $headers = [ + 'Upgrade' => 'websocket', + 'Connection' => 'Upgrade', + 'Sec-websocket-Accept' => $key, + 'Sec-websocket-Version' => '13', + ]; + if (isset($request->header['sec-websocket-protocol'])) { + $headers['Sec-websocket-Protocol'] = $request->header['sec-websocket-protocol']; + } + foreach ($headers as $key => $val) { + $response->header($key, $val); + } + if (isset($request->get['debug']) && $request->get['debug'] == 'test') { + $response->status(101); + $response->end(); + return true; + } else { + return $this->connect($request, $response); + } + } + + /** + * @param Server $server + * @param int $fd + * @throws Exception + */ + public function onClose(Server $server, int $fd) + { + $event = Snowflake::get()->event; + try { + if ($event->exists(Event::SERVER_CLOSE)) { + $event->trigger(Event::SERVER_CLOSE, [$fd]); + } + } catch (\Throwable $exception) { +// $this->addError($exception->getMessage()); + } finally { + $event->trigger(Event::RELEASE_ALL); + Logger::insert(); + } + } + +} diff --git a/http-server/Exception/AuthException.php b/http-server/Exception/AuthException.php new file mode 100644 index 00000000..93a1a546 --- /dev/null +++ b/http-server/Exception/AuthException.php @@ -0,0 +1,26 @@ + $context]; + } else { + static::$_requests[$id][$key] = $context; + } + } else { + static::$_requests[$id] = $context; + } + return $context; + } + + /** + * @param $id + * @param $context + * @param null $key + * @return + */ + private static function setCoroutine($id, $context, $key = null) + { + if (!static::hasContext($id)) { + Coroutine::getContext()[$id] = []; + } + if (!empty($key)) { + if (!is_array(Coroutine::getContext()[$id])) { + Coroutine::getContext()[$id] = [$key => $context]; + } else { + Coroutine::getContext()[$id][$key] = $context; + } + } else { + Coroutine::getContext()[$id] = $context; + } + return $context; + } + + /** + * @param $id + * @param null $key + * @return false|mixed + */ + public static function autoIncr($id, $key = null) + { + if (!static::inCoroutine()) { + return false; + } + if (!isset(Coroutine::getContext()[$id][$key])) { + return false; + } + return Coroutine::getContext()[$id][$key] += 1; + } + + /** + * @param $id + * @param null $key + * @return false|mixed + */ + public static function autoDecr($id, $key = null) + { + if (!static::inCoroutine()) { + return false; + } + if (!isset(Coroutine::getContext()[$id][$key])) { + return false; + } + return Coroutine::getContext()[$id][$key] -= 1; + } + + /** + * @param $id + * @param null $key + * @return mixed + */ + public static function getContext($id, $key = null) + { + if (static::inCoroutine()) { + $array = Coroutine::getContext()[$id] ?? null; + } else { + $array = static::$_requests[$id] ?? null; + } + if (empty($key) || !is_array($array)) { + return $array; + } + return $array[$key]; + } + + /** + * @return mixed + */ + public static function getAllContext() + { + if (static::inCoroutine()) { + return Coroutine::getContext() ?? []; + } else { + return static::$_requests ?? []; + } + } + + /** + * @param $id + * @param null $key + */ + public static function deleteId($id, $key = null) + { + if (!static::hasContext($id, $key)) { + return; + } + if (static::inCoroutine()) { + if (!empty($key)) { + Coroutine::getContext()[$id][$key] = null; + } else { + Coroutine::getContext()[$id] = null; + } + } else { + unset(static::$_requests[$id]); + } + } + + /** + * @param $id + * @param null $key + * @return mixed + */ + public static function hasContext($id, $key = null) + { + if (static::inCoroutine()) { + $data = Coroutine::getContext()[$id] ?? null; + } else { + $data = static::$_requests[$id] ?? null; + } + if (empty($data)) { + return false; + } + if (empty($key)) { + return true; + } else if (!is_array($data)) { + return false; + } + return isset($data[$key]); + } + + + /** + * @return bool + */ + public static function inCoroutine() + { + return Coroutine::getCid() > 0; + } + +} + + + diff --git a/http-server/Http/File.php b/http-server/Http/File.php new file mode 100644 index 00000000..49e24513 --- /dev/null +++ b/http-server/Http/File.php @@ -0,0 +1,94 @@ + 'UPLOAD_ERR_OK.', + 1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini.', + 2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.', + 3 => 'The uploaded file was only partially uploaded.', + 4 => 'No file was uploaded.', + 6 => 'Missing a temporary folder.', + 7 => 'Failed to write file to disk.', + 8 => 'A PHP extension stopped the file upload.' + ]; + + /** + * @param string $path + * @return bool + * @throws Exception + */ + public function saveTo(string $path) + { + if ($this->hasError()) { + throw new Exception($this->getErrorInfo()); + } + + @move_uploaded_file($this->tmp_name, $path); + if (!file_exists($path)) { + return false; + } + return true; + } + + /** + * @return string + */ + public function rename() + { + if (!empty($this->newName)) { + return $this->newName; + } + $param = ['tmp_name' => $this->getTmpPath()]; + $this->newName = \BeReborn::rename($param); + return $this->newName; + } + + /** + * @return string + */ + public function getTmpPath() + { + return $this->tmp_name; + } + + /** + * @return bool + * + * check file have error + */ + public function hasError() + { + return $this->error !== 0; + } + + /** + * @return mixed + * + * get upload error info + */ + public function getErrorInfo() + { + if (!isset($this->errorInfo[$this->error])) { + return 'Unknown upload error.'; + } + return $this->errorInfo[$this->error]; + } +} diff --git a/http-server/Http/Formatter/HtmlFormatter.php b/http-server/Http/Formatter/HtmlFormatter.php new file mode 100644 index 00000000..3758795e --- /dev/null +++ b/http-server/Http/Formatter/HtmlFormatter.php @@ -0,0 +1,59 @@ +data = $data; + return $this; + } + + /** + * @return mixed + */ + public function getData() + { + $data = $this->data; + $this->clear(); + return $data; + } + + public function clear() + { + unset($this->data); + } +} diff --git a/http-server/Http/Formatter/JsonFormatter.php b/http-server/Http/Formatter/JsonFormatter.php new file mode 100644 index 00000000..c41af0c8 --- /dev/null +++ b/http-server/Http/Formatter/JsonFormatter.php @@ -0,0 +1,56 @@ +data = $data; + return $this; + } + + /** + * @return mixed + */ + public function getData() + { + $data = $this->data; + $this->clear(); + return $data; + } + + + public function clear() + { + unset($this->data); + } +} diff --git a/http-server/Http/Formatter/XmlFormatter.php b/http-server/Http/Formatter/XmlFormatter.php new file mode 100644 index 00000000..9c9aefb3 --- /dev/null +++ b/http-server/Http/Formatter/XmlFormatter.php @@ -0,0 +1,87 @@ +'); + + $this->toXml($dom, $data); + + $this->data = $dom->saveXML(); + } + return $this; + } + + /** + * @return string + */ + public function getData() + { + $data = $this->data; + $this->clear(); + return $data; + } + + /** + * @param SimpleXMLElement $dom + * @param $data + */ + public function toXml($dom, $data) + { + foreach ($data as $key => $val) { + if (is_numeric($key)) { + $key = 'item' . $key; + } + if (is_array($val)) { + $node = $dom->addChild($key); + $this->toXml($node, $val); + } else if (is_object($val)) { + $val = get_object_vars($val); + $node = $dom->addChild($key); + $this->toXml($node, $val); + } else { + $dom->addChild($key, htmlspecialchars($val)); + } + } + } + + public function clear() + { + unset($this->data); + } +} diff --git a/http-server/Http/HttpHeaders.php b/http-server/Http/HttpHeaders.php new file mode 100644 index 00000000..d9ee328a --- /dev/null +++ b/http-server/Http/HttpHeaders.php @@ -0,0 +1,136 @@ +headers = $headers; + } + + /** + * @param $name + * @param $value + */ + public function setHeader($name, $value) + { + $this->response[$name] = $value; + } + + /** + * @param array $headers + */ + public function setHeaders(array $headers) + { + foreach ($headers as $key => $val) { + $this->response[$key] = $val; + } + } + + /** + * @param $name + * @param $value + */ + public function replace($name, $value) + { + $this->headers[$name] = $value; + } + + /** + * @param $name + * @param $value + */ + public function addHeader($name, $value) + { + $this->headers[$name] = $value; + } + + /** + * @param array $headers + * @return $this + */ + public function addHeaders(array $headers) + { + if (empty($headers)) { + return $this; + } + if (!empty($this->headers)) { + $headers = array_merge($this->headers, $headers); + } + $this->headers = $headers; + return $this; + } + + /** + * @return array + */ + public function getResponseHeaders() + { + return $this->response; + } + + /** + * @param $name + * @return mixed|null + */ + public function getHeader($name) + { + return $this->headers[$name] ?? null; + } + + + /** + * @param $name + * @return mixed|string|null + */ + public function get($name) + { + return $this->getHeader($name); + } + + + /** + * @param $name + * @return bool + */ + public function exists($name) + { + return isset($this->headers[$name]) && $this->headers[$name] != null; + } + + + /** + * @return array + */ + public function getHeaders() + { + return $this->headers; + } + +} diff --git a/http-server/Http/HttpParams.php b/http-server/Http/HttpParams.php new file mode 100644 index 00000000..f06e4c9b --- /dev/null +++ b/http-server/Http/HttpParams.php @@ -0,0 +1,404 @@ +body = $body; + $this->gets = $get ?? []; + $this->files = $files ?? []; + } + + /** + * @return int + */ + public function offset() + { + return ($this->page() - 1) * $this->size(); + } + + /** + * @param array $data + * 批量添加数据 + */ + public function setPosts($data) + { + if (!is_array($data)) { + return; + } + foreach ($data as $key => $vla) { + $this->body[$key] = $vla; + } + } + + /** + * @param string $key + * @param string $value + */ + public function addGetParam(string $key, string $value) + { + $this->gets[$key] = $value; + } + + /** + * @return int + */ + private function page() + { + return (int)$this->get('page', 1); + } + + /** + * @return int + */ + public function size() + { + return (int)$this->get('size', 20); + } + + + /** + * @param $name + * @param $defaultValue + * @param $call + * @return mixed|null + */ + public function get($name, $defaultValue = null, $call = null) + { + return $this->gets[$name] ?? $defaultValue; + } + + /** + * @param $name + * @param null $defaultValue + * @param $call + * @return mixed|null + */ + public function post($name, $defaultValue = null, $call = null) + { + $data = $this->body[$name] ?? $defaultValue; + if ($call !== null) { + $data = call_user_func($call, $data); + } + return $data; + } + + /** + * @param $name + * @return false|string + * @throws Exception + */ + public function json($name) + { + $data = $this->array($name); + if (empty($data)) { + return JSON::encode([]); + } else if (!is_array($data)) { + return JSON::encode([]); + } + return JSON::encode($data); + } + + /** + * @return array + */ + public function gets() + { + return $this->gets; + } + + /** + * @return array + */ + public function params() + { + return array_merge($this->body ?? [], $this->files ?? []); + } + + /** + * @return array + */ + public function load() + { + return array_merge($this->files, $this->body, $this->gets); + } + + /** + * @param $name + * @param array $defaultValue + * @return array|mixed + */ + public function array($name, $defaultValue = []) + { + return $this->body[$name] ?? $defaultValue; + } + + /** + * @param $name + * @return mixed|File|null + * @throws Exception + */ + public function file($name) + { + if (!isset($this->files[$name])) { + return null; + } + $param = $this->files[$name]; + $param['class'] = File::class; + return \BeReborn::createObject($param); + } + + /** + * @param $name + * @param bool $isNeed + * @return mixed|null + * @throws RequestException + */ + private function required($name, $isNeed = false) + { + $int = $this->body[$name] ?? NULL; + if (is_null($int) && $isNeed === true) { + throw new RequestException("You need to add request parameter $name"); + } + return $int; + } + + /** + * @param $name + * @param bool $isNeed + * @param null $min + * @param null $max + * @return int + * @throws Exception + */ + public function int($name, $isNeed = FALSE, $min = NULL, $max = NULL) + { + $int = $this->required($name, $isNeed); + if ($int === null) return null; + if (is_array($min)) { + list($min, $max) = $min; + } + if (is_null($int)) { + $length = 0; + } else { + $length = strlen(floatval($int)); + } + if (!is_numeric($int) || intval($int) != $int) { + throw new RequestException("The request parameter $name must integer."); + } + $this->between($length, $min, $max); + return (int)$int; + } + + /** + * @param $name + * @param bool $isNeed + * @param int $round + * @return float + * @throws Exception + */ + public function float($name, $isNeed = FALSE, $round = 0) + { + $int = $this->required($name, $isNeed); + if ($int === null) { + return null; + } + if ($round > 0) { + return round(floatval($int), $round); + } else { + return floatval($int); + } + } + + /** + * @param $name + * @param bool $isNeed + * @param null $length + * + * @return string + * @throws + */ + public function string($name, $isNeed = FALSE, $length = NULL) + { + $string = $this->required($name, $isNeed); + if ($string === null || $length === null) { + return $string; + } + if (!is_string($string)) { + $string = json_encode($string, JSON_UNESCAPED_UNICODE); + } + $_length = strlen($string); + if (is_array($length)) { + if (count($length) < 2) { + array_unshift($length, 0); + } + $this->between($_length, ...$length); + } else if (is_numeric($length) && $_length != $length) { + throw new RequestException("The length of the string must be $length characters"); + } + return $string; + } + + /** + * @param $_length + * @param $min + * @param $max + * @throws RequestException + */ + private function between($_length, $min, $max) + { + if ($min !== NULL && $_length < $min) { + throw new RequestException("The minimum value cannot be lower than $min"); + } + if ($max !== NULL && $_length > $max) { + throw new RequestException("Maximum cannot exceed $max, has length " . $_length); + } + } + + /** + * @param $name + * @param bool $isNeed + * + * @return string + * @throws RequestException + */ + public function email($name, $isNeed = FALSE) + { + $email = $this->required($name, $isNeed); + if ($email === null) { + return null; + } + if (!preg_match('/^\w+([.-_]\w+)+@\w+(\.\w+)+$/', $email)) { + throw new RequestException("Request parameter $name is in the wrong format", 4001); + } + return $email; + } + + + /** + * @param $name + * @param bool $isNeed + * + * @return string + * @throws RequestException + */ + public function bool($name, $isNeed = FALSE) + { + $email = $this->required($name, $isNeed); + if ($email === null) { + return false; + } + return (bool)$email; + } + + /** + * @param $name + * @param null $default + * + * @return mixed|null + * @throws RequestException + */ + public function timestamp($name, $default = NULL) + { + $value = $this->required($name, false); + if ($value === null) { + return $default; + } + if (!is_numeric($value)) { + throw new RequestException('The request param :attribute not is a timestamp value'); + } + if (strlen((string)$value) != 10) { + throw new RequestException('The request param :attribute not is a timestamp value'); + } + if (!date('YmdHis', $value)) { + throw new RequestException('The request param :attribute format error', 4001); + } + return $value; + } + + /** + * @param $name + * @param null $default + * + * @return mixed|null + * @throws RequestException + */ + public function datetime($name, $default = NULL) + { + $value = $this->required($name, false); + if ($value === null) { + return $default; + } + $match = '/^\d{4}.*?([1-12]).*([1-31]).*?[0-23].*?[0-59].*?[0-59].*?$/'; + $match = preg_match($match, $value, $result); + if (!$match || $result[0] != $value) { + throw new RequestException('The request param :attribute format error', 4001); + } + return $value; + } + + + /** + * @param $name + * @param null $default + * @return mixed|null + * @throws RequestException + */ + public function ip($name, $default = NULL) + { + $value = $this->required($name, false); + if ($value == NULL) { + return $default; + } + $match = preg_match('/^\d{1,3}(\.\d{1,3}){3}$/', $value, $result); + if (!$match || $result[0] != $value) { + throw new RequestException('The request param :attribute format error', 4001); + } + return $value; + } + + + /** + * @param $name + * @return mixed|null + */ + public function __get($name) + { + $load = $this->load(); + + return $load[$name] ?? null; + } + +} diff --git a/http-server/Http/Request.php b/http-server/Http/Request.php new file mode 100644 index 00000000..f936e7b1 --- /dev/null +++ b/http-server/Http/Request.php @@ -0,0 +1,424 @@ +fd = $fd; + } + + /** + * @return bool + */ + public function isFavicon() + { + return $this->getUri() === 'favicon.ico'; + } + + /** + * @return mixed + */ + public function getIdentity() + { + return $this->_grant; + } + + /** + * @return bool + */ + public function isHead() + { + $result = $this->headers->getHeader('request_method') == 'head'; + if ($result) { + $this->setStatus(101); + } else { + $this->setStatus(200); + } + return $result; + } + + /** + * @param $status + * @return mixed + */ + public function setStatus($status) + { + return $this->statusCode = $status; + } + + /** + * @return int + */ + public function getStatus() + { + return $this->statusCode; + } + + /** + * @return bool + */ + public function getIsPackage() + { + return $this->headers->getHeader('request_method') == 'package'; + } + + /** + * @return bool + */ + public function getIsReceive() + { + return $this->headers->getHeader('request_method') == 'receive'; + } + + + /** + * @param $value + */ + public function setGrantAuthorization($value) + { + $this->_grant = $value; + } + + + /** + * @return bool + */ + public function hasGrant() + { + return $this->_grant !== null; + } + + + /** + * @return string + */ + public function parseUri() + { + $array = []; + $explode = explode('/', $this->headers->getHeader('request_uri')); + foreach ($explode as $item) { + if (empty($item)) { + continue; + } + $array[] = $item; + } + return $this->uri = implode('/', ($this->explode = $array)); + } + + /** + * @return string[] + */ + public function getExplode() + { + return $this->explode; + } + + /** + * @return mixed|string + */ + public function getCurrent() + { + return current($this->explode); + } + + /** + * @return string + */ + public function getUri() + { + if (!$this->headers) { + return 'command exec.'; + } + if (!empty($this->uri)) { + return $this->uri; + } + $uri = $this->headers->getHeader('request_uri'); + $uri = ltrim($uri, '/'); + if (empty($uri)) return '/'; + return $uri; + } + + + /** + * @return mixed|string + * @throws Exception + */ + public function adapter() + { + if (!$this->isHead()) { + return router()->runHandler(); + } + return ''; + } + + + /** + * @return string|null + */ + public function getPlatform() + { + $user = $this->headers->getHeader('user-agent'); + $match = preg_match('/\(.*\)?/', $user, $output); + if (!$match || count($output) < 1) { + return null; + } + $output = strtolower(array_shift($output)); + if (strpos('mac', $output)) { + return 'mac'; + } else if (strpos('iphone', $output)) { + return 'iphone'; + } else if (strpos('android', $output)) { + return 'android'; + } else if (strpos('windows', $output)) { + return 'windows'; + } + return null; + } + + /** + * @return bool + */ + public function isIos() + { + return $this->getPlatform() == static::PLATFORM_IPHONE; + } + + /** + * @return bool + */ + public function isAndroid() + { + return $this->getPlatform() == static::PLATFORM_ANDROID; + } + + /** + * @return bool + */ + public function isMacOs() + { + return $this->getPlatform() == static::PLATFORM_MAC_OX; + } + + /** + * @return bool + */ + public function isWindows() + { + return $this->getPlatform() == static::PLATFORM_WINDOWS; + } + + /** + * @return bool + */ + public function getIsPost() + { + return $this->getMethod() == 'post'; + } + + /** + * @return bool + * @throws Exception + */ + public function getIsHttp() + { + if (!app()->has('socket')) { + return false; + } + $socket = \BeReborn::$app->getSocket()->getServer(); + if (empty($this->fd)) { + return false; + } + return $socket->exist($this->fd) && !$socket->isEstablished($this->fd); + } + + /** + * @return bool + */ + public function getIsOption() + { + return $this->getMethod() == 'options'; + } + + /** + * @return bool + */ + public function getIsGet() + { + return $this->getMethod() == 'get'; + } + + /** + * @return bool + */ + public function getIsDelete() + { + return $this->getMethod() == 'delete'; + } + + /** + * @return string + * + * 获取请求类型 + */ + public function getMethod() + { + $head = $this->headers->getHeader('request_method'); + return strtolower($head); + } + + /** + * @return bool + */ + public function getIsCli() + { + return $this->isCli === TRUE; + } + + + /** + * @param $name + * @param $value + * + * @throws Exception + */ + public function __set($name, $value) + { + $method = 'set' . ucfirst($name); + if (method_exists($this, $method)) { + $this->$method($value); + } else { + parent::__set($name, $value); // TODO: Change the autogenerated stub + } + } + + /** + * @return mixed|null + */ + public function getIp() + { + $headers = $this->headers->getHeaders(); + if (!empty($headers['x-forwarded-for'])) return $headers['x-forwarded-for']; + if (!empty($headers['request-ip'])) return $headers['request-ip']; + if (!empty($headers['remote_addr'])) return $headers['remote_addr']; + return NULL; + } + + /** + * @return string + */ + public function getRuntime() + { + return sprintf('%.5f', microtime(TRUE) - $this->startTime); + } + + /** + * @return string + */ + public function getDebug() + { + $mainstay = sprintf("%.6f", microtime(true)); // 带毫秒的时间戳 + + $timestamp = floor($mainstay); // 时间戳 + $milliseconds = round(($mainstay - $timestamp) * 1000); // 毫秒 + + $datetime = date("Y-m-d H:i:s", $timestamp) . '.' . $milliseconds; + + $tmp = [ + '[Debug ' . $datetime . '] ', + $this->getIp(), + $this->getUri(), + '`' . $this->headers->getHeader('user-agent') . '`', + $this->getRuntime() + ]; + + return implode(' ', $tmp); + } + + + /** + * @param $request + * @return Request + */ + public static function create($request) + { + $sRequest = new Request(); + $sRequest->fd = $request->fd; + $sRequest->startTime = microtime(true); + $sRequest->params = new HttpParams(Help::toArray($request->rawContent()), $request->get, $request->files); + if (!empty($request->post)) { + $sRequest->params->setPosts($request->post ?? []); + } + $headers = $request->server; + if (!empty($request->header)) { + $headers = array_merge($headers, $request->header); + } + $sRequest->headers = new HttpHeaders($headers); + $sRequest->parseUri(); + return $sRequest; + } + + +} diff --git a/http-server/Http/Response.php b/http-server/Http/Response.php new file mode 100644 index 00000000..3156e274 --- /dev/null +++ b/http-server/Http/Response.php @@ -0,0 +1,226 @@ + JsonFormatter::class, + self::XML => XmlFormatter::class, + self::HTML => HtmlFormatter::class + ]; + + public $fd = 0; + + /** + * @param $format + * @return $this + */ + public function setFormat($format) + { + $this->format = $format; + return $this; + } + + /** + * 清理无用数据 + */ + public function clear() + { + $this->fd = 0; + $this->isWebSocket = false; + $this->format = null; + } + + /** + * @return string + */ + public function getContentType() + { + if ($this->format == null || $this->format == static::JSON) { + return 'application/json;charset=utf-8'; + } else if ($this->format == static::XML) { + return 'application/xml;charset=utf-8'; + } else { + return 'text/html;charset=utf-8'; + } + } + + /** + * @return mixed + * @throws Exception + */ + public function sender() + { + return $this->send(func_get_args()); + } + + /** + * @param $key + * @param $value + */ + public function addHeader($key, $value) + { + $response = Context::getContext('response'); + $response->header($key, $value); + } + + /** + * @param string $context + * @param int $statusCode + * @param null $response + * @return bool + * @throws Exception + */ + public function send($context = '', $statusCode = 200, $response = null) + { + $sendData = $this->parseData($context); + if ($response instanceof SResponse) { + $this->response = $response; + } + if ($this->response instanceof SResponse) { + return $this->sendData($this->response, $sendData, $statusCode); + } else { + return $this->printResult($sendData); + } + } + + /** + * @param $context + * @return mixed + * @throws Exception + */ + private function parseData($context) + { + if (isset($this->_format_maps[$this->format])) { + $config['class'] = $this->_format_maps[$this->format]; + } else { + $config['class'] = HtmlFormatter::class; + } + $formatter = Snowflake::createObject($config); + return $formatter->send($context)->getData(); + } + + /** + * @param $result + * @return string + * @throws Exception + */ + private function printResult($result) + { + $result = Help::toString($result); + + $string = 'Command Result: ' . PHP_EOL; + $string .= empty($result) ? 'success!' : $result . PHP_EOL; + $string .= 'Command Success!' . PHP_EOL; + echo $string; + + $event = Snowflake::get()->event; + $event->trigger('CONSOLE_END'); + + return 'ok'; + } + + /** + * @param $response + * @param $sendData + * @param $status + * @return mixed + */ + private function sendData($response, $sendData, $status) + { + $response->status($status); + $response->header('Content-Type', $this->getContentType()); + $response->header('Access-Control-Allow-Origin', '*'); + $response->header('Run-Time', $this->getRuntime()); + return $response->end($sendData); + } + + /** + * @param $url + * @param array $param + * @return int + */ + public function redirect($url, array $param = []) + { + if (!empty($param)) { + $url .= '?' . http_build_query($param); + } + $url = ltrim($url, '/'); + if (!preg_match('/^http/', $url)) { + $url = '/' . $url; + } + return $this->response->redirect($url); + } + + /** + * @param null $response + * @return mixed + * @throws ComponentException + */ + public static function create($response = null) + { + $ciResponse = Snowflake::get()->clone('response'); + $ciResponse->response = $response; + $ciResponse->startTime = microtime(true); + $ciResponse->format = self::JSON; + return $ciResponse; + } + + + /** + * @throws Exception + */ + public function sendNotFind() + { + $this->format = static::HTML; + $this->send('', 404); + } + + /** + * @return string + */ + public function getRuntime() + { + return sprintf('%.5f', microtime(TRUE) - $this->startTime); + } + +} diff --git a/http-server/IInterface/AuthIdentity.php b/http-server/IInterface/AuthIdentity.php new file mode 100644 index 00000000..e65ef7ed --- /dev/null +++ b/http-server/IInterface/AuthIdentity.php @@ -0,0 +1,18 @@ +nodes = $nodes; + } + + + /** + * @param $name + * @param $arguments + * @return $this + */ + public function __call($name, $arguments) + { + foreach ($this->nodes as $node) { + $node->{$name}(...$arguments); + } + return $this; + } + +} diff --git a/http-server/Route/CoreMiddleware.php b/http-server/Route/CoreMiddleware.php new file mode 100644 index 00000000..3d07ea37 --- /dev/null +++ b/http-server/Route/CoreMiddleware.php @@ -0,0 +1,39 @@ +headers; + + /** @var Response $response */ + $response = \BeReborn::getApp('response'); + $request_method = $header->getHeader('access-control-request-method'); + $request_headers = $header->getHeader('access-control-request-headers'); + $response->addHeader('Access-Control-Allow-Headers', $request_headers); + $response->addHeader('Access-Control-Request-Method', $request_method); + + return $next($request); + } + +} diff --git a/http-server/Route/Dispatch/Dispatch.php b/http-server/Route/Dispatch/Dispatch.php new file mode 100644 index 00000000..42677979 --- /dev/null +++ b/http-server/Route/Dispatch/Dispatch.php @@ -0,0 +1,74 @@ +handler = $handler; + $class->request = $request; + if ($handler instanceof \Closure) { + $class->bind(); + } + $class->bindParam(); + return $class; + } + + + /** + * @return mixed + * 执行函数 + */ + public function dispatch() + { + return call_user_func($this->handler, $this->request); + } + + + /** + * 设置作用域 + */ + protected function bind() + { + $this->handler = \Closure::bind($this->handler, new Controller()); + } + + + /** + * 参数绑定 + */ + protected function bindParam() + { + /** @var Controller $controller */ + if (is_array($this->handler)) { + $controller = $this->handler[0]; + } else { + $controller = $this->handler; + } + $request = \BeReborn::getApp('request'); + $controller->setRequest($request); + $controller->setHeaders($request->headers); + $controller->setInput($request->params); + } + +} diff --git a/http-server/Route/Filter.php b/http-server/Route/Filter.php new file mode 100644 index 00000000..1ea818a1 --- /dev/null +++ b/http-server/Route/Filter.php @@ -0,0 +1,129 @@ +rules = []; + $class->params = Input()->params(); + + return $this->_filters[] = $class; + } + + + /** + * @param array $value + * @return HeaderFilter|bool + * @throws Exception + */ + public function setHeader(array $value) + { + if (empty($value)) { + return true; + } + + /** @var HeaderFilter $class */ + $class = \BeReborn::createObject(HeaderFilter::class); + $class->rules = []; + $class->params = request()->headers->getHeaders(); + + return $this->_filters[] = $class; + } + + + /** + * @param array $value + * @return QueryFilter|bool + * @throws Exception + */ + public function setQuery(array $value) + { + if (empty($value)) { + return true; + } + + /** @var QueryFilter $class */ + $class = \BeReborn::createObject(QueryFilter::class); + $class->rules = []; + $class->params = request()->headers->getHeaders(); + + return $this->_filters[] = $class; + } + + + /** + * @throws Exception + */ + public function handler() + { + if (($error = $this->filters()) !== true) { + throw new FilterException($error); + } + if (!$this->grant()) { + throw new AuthException('Authentication error.'); + } + return true; + } + + /** + * @return bool + */ + private function filters() + { + if (empty($this->_filters)) { + return true; + } + foreach ($this->_filters as $filter) { + if (!$filter->check()) { + return false; + } + } + return true; + } + + /** + * @return bool|mixed + */ + private function grant() + { + if (!is_callable($this->grant, true)) { + return true; + } + return call_user_func($this->grant); + } + +} diff --git a/http-server/Route/Filter/BodyFilter.php b/http-server/Route/Filter/BodyFilter.php new file mode 100644 index 00000000..f5ddb425 --- /dev/null +++ b/http-server/Route/Filter/BodyFilter.php @@ -0,0 +1,25 @@ +validator(); + } + +} diff --git a/http-server/Route/Filter/Filter.php b/http-server/Route/Filter/Filter.php new file mode 100644 index 00000000..43fd1857 --- /dev/null +++ b/http-server/Route/Filter/Filter.php @@ -0,0 +1,46 @@ +setParams($this->params); + foreach ($this->rules as $val) { + $field = array_shift($val); + if (empty($val)) { + continue; + } + $validator->make($field, $val); + } + if (!$validator->validation()) { + return $this->addError($validator->getError()); + } + return true; + } + +} diff --git a/http-server/Route/Filter/FilterException.php b/http-server/Route/Filter/FilterException.php new file mode 100644 index 00000000..837c28f3 --- /dev/null +++ b/http-server/Route/Filter/FilterException.php @@ -0,0 +1,17 @@ +validator(); + } + +} diff --git a/http-server/Route/Filter/QueryFilter.php b/http-server/Route/Filter/QueryFilter.php new file mode 100644 index 00000000..914161a5 --- /dev/null +++ b/http-server/Route/Filter/QueryFilter.php @@ -0,0 +1,25 @@ +validator(); + } + +} diff --git a/http-server/Route/Handler.php b/http-server/Route/Handler.php new file mode 100644 index 00000000..da5d4726 --- /dev/null +++ b/http-server/Route/Handler.php @@ -0,0 +1,51 @@ +router = \BeReborn::$app->getRouter(); + + parent::__construct([]); + } + + /** + * @param $config + * @param $handler + */ + public function group($config, $handler) + { + $this->router->group($config, $handler, $this); + } + + + /** + * @param $route + * @param $handler + * @return Handler + */ + public function handler($route, $handler) + { + return $this->router->addRoute($route, $handler, 'receive'); + } + +} diff --git a/http-server/Route/Limits.php b/http-server/Route/Limits.php new file mode 100644 index 00000000..6924e7c8 --- /dev/null +++ b/http-server/Route/Limits.php @@ -0,0 +1,69 @@ +route[$path] = [$limit, $duration, $isBindConsumer]; + return $this; + } + + /** + * @param int $userId + * @return bool + * @throws Exception + * + * 判断有没有被限流 + */ + public function isRestrictedCurrent(int $userId = 0) + { + $path = \request()->getUri(); + if (!isset($this->route[$path])) { + return false; + } + $redis = \BeReborn::getRedis(); + [$limit, $duration, $isBindConsumer] = $this->route[$path]; + if ($limit < 1) { + return false; + } + if ($isBindConsumer && $userId < 1) { + return true; + } + + $uri = md5($path) . '_' . $userId; + if ($redis->incr($uri) > $limit) { + return true; + } + if ($redis->ttl($uri) == -1) { + $redis->expire($uri, $duration); + } + return false; + } + + +} diff --git a/http-server/Route/Middleware.php b/http-server/Route/Middleware.php new file mode 100644 index 00000000..6a7fe448 --- /dev/null +++ b/http-server/Route/Middleware.php @@ -0,0 +1,77 @@ +middleWares[] = $call; + return $this; + } + + /** + * @param array $array + * @return $this + */ + public function setMiddleWares(array $array) + { + $this->middleWares = $array; + return $this; + } + + /** + * @param $dispatch + * @return mixed + * @throws Exception + */ + public function getGenerate($dispatch) + { + $last = function ($passable) use ($dispatch) { + return Dispatch::create($dispatch, $passable)->dispatch(); + }; + $data = array_reduce(array_reverse($this->middleWares), $this->core(), $last); + $this->middleWares = []; + return $data; + } + + /** + * @return Closure + */ + public function core() + { + return function ($stack, $pipe) { + return function ($passable) use ($stack, $pipe) { + if ($pipe instanceof IMiddleware) { + return $pipe->handler($passable, $stack); + } else { + return $pipe($passable, $stack); + } + }; + }; + } + +} diff --git a/http-server/Route/Node.php b/http-server/Route/Node.php new file mode 100644 index 00000000..44c9be6b --- /dev/null +++ b/http-server/Route/Node.php @@ -0,0 +1,313 @@ +handler = $handler; + } else if (is_string($handler) && strpos($handler, '@') !== false) { + list($controller, $action) = explode('@', $handler); + if (!empty($this->namespace)) { + $controller = implode('\\', $this->namespace) . '\\' . $controller; + } + $this->handler = $this->getReflect($controller, $action); + } else if ($handler != null && !is_callable($handler, true)) { + $this->_error = 'Controller is con\'t exec.'; + } else { + $this->handler = $handler; + } + return $this->newExec(); + } + + /** + * @param $request + * @return bool + */ + public function methodAllow(Request $request) + { + if ($this->method == $request->getMethod()) { + return true; + } + return $this->method == 'any'; + } + + /** + * @return bool + * @throws Exception + */ + public function checkSuffix() + { + if ($this->enableHtmlSuffix) { + $url = request()->getUri(); + $nowLength = strlen($this->htmlSuffix); + if (strpos($url, $this->htmlSuffix) !== strlen($url) - $nowLength) { + return false; + } + } + return $this->checkRule(); + } + + /** + * @return bool + * @throws Exception + */ + private function checkRule() + { + if (empty($this->rules)) { + return true; + } + foreach ($this->rules as $rule) { + if (!isset($rule['class'])) { + $rule['class'] = Filter::class; + } + /** @var Filter $object */ + $object = \BeReborn::createObject($rule); + if (!$object->handler()) { + return false; + }; + } + return true; + } + + /** + * @param string $controller + * @param string $action + * @return null|array + * @throws Exception + */ + private function getReflect(string $controller, string $action) + { + try { + $reflect = new \ReflectionClass($controller); + if (!$reflect->isInstantiable()) { + throw new Exception($controller . ' Class is con\'t Instantiable.'); + } + + if (!empty($action) && !$reflect->hasMethod($action)) { + throw new Exception('method ' . $action . ' not exists at ' . $controller . '.'); + } + return [$reflect->newInstance(), $action]; + } catch (Exception $exception) { + $this->_error = $exception->getMessage(); + $this->error($exception->getMessage(), 'router'); + return null; + } + } + + /** + * @return string + * 错误信息 + */ + public function getError() + { + return $this->_error; + } + + /** + * @param Node $node + * @param string $field + * @return Node + */ + public function addChild(Node $node, string $field) + { + /** @var Node $oLod */ + $oLod = $this->childes[$field] ?? null; + if (!empty($oLod)) { + $node = $oLod; + } + $this->childes[$field] = $node; + return $this->childes[$field]; + } + + /** + * @param $rule + * @return $this + */ + public function filter($rule) + { + if (empty($rule)) { + return $this; + } + if (!isset($rule[0])) { + $rule = [$rule]; + } + foreach ($rule as $value) { + if (empty($value)) { + continue; + } + $this->rules[] = $value; + } + return $this; + } + + /** + * @param string $search + * @return Node|mixed + */ + public function findNode(string $search) + { + if (empty($this->childes)) { + return null; + } + + if (isset($this->childes[$search])) { + return $this->childes[$search]; + } + + $_searchMatch = '/<(\w+)?:(.+)?>/'; + foreach ($this->childes as $key => $val) { + if (preg_match($_searchMatch, $key, $match)) { + \Input()->addGetParam($match[1] ?? '--', $search); + return $this->childes[$key]; + } + } + return null; + } + + /** + * @param $options + * @return $this + */ + public function bindOptions($options) + { + if (is_object($options)) { + $this->options = $options; + } else { + $options = array_filter($options); + $last = $options[count($options) - 1]; + if (empty($last)) { + return $this; + } + $this->options = $last; + } + return $this; + } + + /** + * @param string $alias + * @return $this + * 别称 + */ + public function alias(string $alias) + { + $_alias = $alias; + return $this; + } + + + /** + * @param int $limit + * @param int $duration + * @param bool $isBindConsumer + * @return $this + * @throws Exception + */ + public function limits(int $limit, int $duration = 60, bool $isBindConsumer = false) + { + $limits = \BeReborn::$app->getLimits(); + $limits->addLimits($this->path, $limit, $duration, $isBindConsumer); + return $this; + } + + /** + * @param $middles + * @throws + */ + public function bindMiddleware(array $middles) + { + $_tmp = []; + if (empty($middles)) { + return; + } + foreach ($middles as $middle) { + if (empty($middle)) { + continue; + } + try { + if (is_array($middle)) { + $_tmp = $this->each($middle, $_tmp); + } else { + $_tmp[] = \BeReborn::createObject($middle); + } + } catch (Exception $exception) { + } + } + $this->middleware = $_tmp; + $this->newExec(); + } + + + /** + * @throws Exception + */ + private function newExec() + { + if (!empty($this->handler)) { + $made = new Middleware(); + $made->setMiddleWares($this->middleware); + $this->callback = $made->getGenerate($this->handler); + } + return $this; + } + + + /** + * @param $array + * @param $_temp + * @return array + * @throws Exception + */ + private function each($array, $_temp) + { + if (empty($array)) { + return $_temp; + } + foreach ($array as $class) { + if (is_array($class)) { + $_temp = $this->each($class, $_temp); + } else { + $_temp[] = \BeReborn::createObject($class); + } + } + return $_temp; + } +} diff --git a/http-server/Route/Router.php b/http-server/Route/Router.php new file mode 100644 index 00000000..ff6a5d2d --- /dev/null +++ b/http-server/Route/Router.php @@ -0,0 +1,504 @@ +dir = Config::get('controller.path', false, $this->dir); + } + + /** + * @param $path + * @param $handler + * @param string $method + * @return mixed|Node|null + * @throws + */ + public function addRoute($path, $handler, $method = 'any') + { + if (!isset($this->nodes[$method])) { + $this->nodes[$method] = []; + } + + list($first, $explode) = $this->split($path); + $parent = $this->nodes[$method][$first] ?? null; + if ($handler instanceof \Closure) { + $handler = Closure::bind($handler, new Controller()); + } + + if (empty($parent)) { + $parent = $this->NodeInstance($first, 0, $method); + $this->nodes[$method][$first] = $parent; + } + if ($first === '/') { + return $parent->bindHandler($handler); + } + + $parent = $this->bindNode($parent, $explode, $method); + return $parent->bindHandler($handler); + } + + /** + * @param Node $parent + * @param array $explode + * @param $method + * @return Node + */ + private function bindNode($parent, $explode, $method) + { + $a = 0; + if (empty($explode)) { + return $parent->addChild($this->NodeInstance('/', $a, $method), '/'); + } + foreach ($explode as $value) { + if (empty($value)) { + continue; + } + ++$a; + + $search = $parent->findNode($value); + if ($search === null) { + $parent = $parent->addChild($this->NodeInstance($value, $a, $method), $value); + } else { + $parent = $search; + } + } + return $parent; + } + + /** + * @param $route + * @param $handler + * @return Node|mixed|null + */ + public function socket($route, $handler) + { + return $this->addRoute($route, $handler, 'socket'); + } + + + /** + * @param $route + * @param $handler + * @param int $port + * @return Node|mixed|null + */ + public function gRpc($route, $handler, $port = 33007) + { + $route = ltrim($route, '/'); + if (!empty($port)) { + $route = $port . '/' . $route; + } + return $this->addRoute($route, $handler, 'grpc'); + } + + + /** + * @param $route + * @param $handler + * @return Node|mixed|null + */ + public function task($route, $handler) + { + return $this->addRoute($route, $handler, 'Task'); + } + + /** + * @param $route + * @param $handler + * @return mixed|Node|null + * @throws + */ + public function post($route, $handler) + { + return $this->addRoute($route, $handler, 'post'); + } + + /** + * @param $route + * @param $handler + * @return mixed|Node|null + * @throws + */ + public function get($route, $handler) + { + return $this->addRoute($route, $handler, 'get'); + } + + /** + * @param $route + * @param $handler + * @return mixed|Node|null + * @throws + */ + public function options($route, $handler) + { + return $this->addRoute($route, $handler, 'options'); + } + + + /** + * @param $port + * @param Closure $closure + * @throws + */ + public function listen(int $port, Closure $closure) + { + $stdClass = \BeReborn::createObject(Handler::class); + $this->group(['prefix' => $port], $closure, $stdClass); + } + + /** + * @param $route + * @param $handler + * @return Any + */ + public function any($route, $handler) + { + $nodes = []; + foreach (['get', 'post', 'options', 'put', 'delete'] as $method) { + $nodes[] = $this->addRoute($route, $handler, $method); + } + return new Any($nodes); + } + + /** + * @param $route + * @param $handler + * @return mixed|Node|null + * @throws + */ + public function delete($route, $handler) + { + return $this->addRoute($route, $handler, 'delete'); + } + + /** + * @param $route + * @param $handler + * @return mixed|Node|null + * @throws + */ + public function put($route, $handler) + { + return $this->addRoute($route, $handler, 'put'); + } + + /** + * @param $value + * @param $index + * @param $method + * @return Node + * @throws + */ + public function NodeInstance($value, $index = 0, $method = 'get') + { + $node = new Node(); + $node->childes = []; + $node->path = $value; + $node->index = $index; + $node->method = $method; + + $name = array_column($this->groupTacks, 'namespace'); + + $dir = array_column($this->groupTacks, 'dir'); + if (!empty($dir)) { + array_unshift($name, implode('\\', $dir)); + } else { + if ($method == 'receive') { + $dir = 'App\\Tcp'; + } else if ($method == 'package') { + $dir = 'App\\Udp'; + } else { + $dir = $this->dir; + } + array_unshift($name, $dir); + } + + if (!empty($name) && $name = array_filter($name)) { + $node->namespace = $name; + } + + $name = array_column($this->groupTacks, 'middleware'); + if (!empty($name) && $name = array_filter($name)) { + $node->bindMiddleware($name); + } + + $options = array_column($this->groupTacks, 'options'); + if (!empty($options) && is_array($options)) { + $node->bindOptions($options); + } + + $rules = array_column($this->groupTacks, 'filter'); + $rules = array_shift($rules); + if (!empty($rules) && is_array($rules)) { + $node->filter($rules); + } + + return $node; + } + + /** + * @param array $config + * @param callable $callback + * 路由分组 + * @param null $stdClass + */ + public function group(array $config, callable $callback, $stdClass = null) + { + $this->groupTacks[] = $config; + if ($stdClass) { + $callback($stdClass); + } else { + $callback($this); + } + array_pop($this->groupTacks); + } + + /** + * @return string + */ + public function addPrefix() + { + $prefix = array_column($this->groupTacks, 'prefix'); + + $prefix = array_filter($prefix); + + if (empty($prefix)) { + return ''; + } + + return '/' . implode('/', $prefix); + } + + /** + * @param array $explode + * @param $method + * @return Node|null + * 查找指定路由 + */ + public function tree_search($explode, $method) + { + if (empty($explode)) { + return $this->nodes[$method]['/'] ?? null; + } + $first = array_shift($explode); + if (!($parent = $this->nodes[$method][$first] ?? null)) { + return null; + } + if (empty($explode)) { + return $parent->findNode('/'); + } + while ($value = array_shift($explode)) { + $node = $parent->findNode($value); + if (!$node) { + break; + } + $parent = $node; + } + return $parent; + } + + /** + * @param $path + * @return array + * '*' + */ + public function split($path) + { + $prefix = $this->addPrefix(); + $path = ltrim($path, '/'); + if (!empty($prefix)) { + $path = $prefix . '/' . $path; + } + + $explode = array_filter(explode('/', $path)); + if (empty($explode)) { + return ['/', []]; + } + + $first = array_shift($explode); + if (empty($explode)) { + $explode = []; + } + return [$first, $explode]; + } + + /** + * @return array + */ + public function each() + { + $paths = []; + foreach ($this->nodes as $node) { + /** @var Node[] $node */ + foreach ($node as $_node) { + if ($_node->path == '/') { + continue; + } + $path = strtoupper($_node->method) . ' : ' . $_node->path; + if (!empty($_node->childes)) { + $path = $this->readByChild($_node->childes, $path); + } + $paths[] = $path; + } + } + return $this->readByArray($paths); + } + + /** + * @param $array + * @param array $returns + * @return array + */ + private function readByArray($array, $returns = []) + { + foreach ($array as $value) { + if (empty($value)) { + continue; + } + if (is_array($value)) { + $returns = $this->readByArray($value, $returns); + } else { + [$method, $route] = explode(' : ', $value); + + $returns[] = ['method' => $method, 'route' => $route]; + } + } + return $returns; + } + + + /** + * @param $child + * @param string $paths + * @return array + */ + private function readByChild($child, $paths = '') + { + $newPath = []; + /** @var Node $item */ + foreach ($child as $item) { + if ($item->path == '/') { + continue; + } + if (!empty($item->childes)) { + $newPath[] = $this->readByChild($item->childes, $paths . '/' . $item->path); + } else { + [$first, $route] = explode(' : ', $paths); + + $newPath[] = strtoupper($item->method) . ' : ' . $route . '/' . $item->path; + } + + } + return $newPath; + } + + /** + * @return mixed + * @throws + */ + public function dispatch() + { + $request = Context::getContext('request'); + if (!($node = $this->find_path($request))) { + return JSON::to(404, 'Page not found.'); + } + if (empty($node->callback)) { + return JSON::to(404, 'Page not found.'); + } + return call_user_func($node->callback, $request); + } + + /** + * @param $request + * @return Node|false|int|mixed|string|null + */ + private function find_path($request) + { + $node = $this->tree_search($request->getExplode(), $request->getMethod()); + if ($node instanceof Node) { + return $node; + } + if (!$request->isOption) { + return null; + } + $node = $this->tree_search(['*'], $request->getMethod()); + if (!($node instanceof Node)) { + return null; + } + return $node; + } + + /** + * @throws + */ + public function loader() + { + try { + $this->loadDir(APP_PATH . '/routes'); + } catch (Exception $exception) { + $this->error($exception->getMessage()); + } + } + + /** + * @param $path + * @throws Exception + * 加载目录下的路由文件 + */ + private function loadDir($path) + { + try { + $files = glob($path . '/*'); + for ($i = 0; $i < count($files); $i++) { + if (is_dir($files[$i])) { + $this->loadDir($files[$i]); + } else { + $this->loadFile($files[$i]); + } + } + } catch (Exception $exception) { + $this->error($exception->getMessage()); + } + } + + /** + * @param $file + */ + private function loadFile($file) + { + $router = $this; + include_once "Router.php"; + } + +} diff --git a/http-server/Server.php b/http-server/Server.php new file mode 100644 index 00000000..06b8beb2 --- /dev/null +++ b/http-server/Server.php @@ -0,0 +1,127 @@ + '127.0.0.1', 'port'=> 5775, 'mode'=> SWOOLE_TCP], + * ['host'=> '127.0.0.1', 'port'=> 5775, 'mode'=> SWOOLE_TCP], + * ['host'=> '127.0.0.1', 'port'=> 5775, 'mode'=> SWOOLE_TCP], + * ['host'=> '127.0.0.1', 'port'=> 5775, 'mode'=> SWOOLE_TCP], + * ['host'=> '127.0.0.1', 'port'=> 5775, 'mode'=> SWOOLE_UDP], + * ['host'=> '127.0.0.1', 'port'=> 5775, 'mode'=> SWOOLE_TCP] + * ] + */ +class Server extends Application +{ + const HTTP = 'HTTP'; + const TCP = 'TCP'; + const PACKAGE = 'PACKAGE'; + const WEBSOCKET = 'WEBSOCKET'; + + private $server = [ + 'HTTP' => [SWOOLE_TCP, Http::class], + 'TCP' => [SWOOLE_TCP, Receive::class], + 'PACKAGE' => [SWOOLE_UDP, Packet::class], + 'WEBSOCKET' => [SWOOLE_SOCK_TCP, WebSocket::class], + ]; + + /** + * @param array $configs + * @return array + * @throws Exception + */ + public function initCore(array $configs) + { + $response = []; + foreach ($configs as $server) { + $response[] = $this->create($server); + } + return $response; + } + + + /** + * @param $config + * @return mixed + * @throws Exception + */ + private function create($config) + { + $settings = $config['settings'] ?? []; + if (!isset($this->server[$config['type']])) { + throw new Exception('Unknown server type(' . $config['type'] . ').'); + } + $server = $this->dispatchCreate($config, $settings); + if (isset($config['events'])) { + $this->createEventListen($config); + } + return $server; + } + + + /** + * @param $config + */ + protected function createEventListen($config) + { + if (!is_array($config['events'])) { + return; + } + $event = Snowflake::get()->event; + foreach ($config['events'] as $name => $_event) { + $event->on($name, $_event); + } + } + + /** + * @param $config + * @param $settings + * @return mixed + * @throws Exception + */ + private function dispatchCreate($config, $settings) + { + switch ($config['type']) { + case self::HTTP: + $handler = [ + ['request', [Http::class, 'onHandler']] + ]; + break; + case self::TCP: + $handler = [ + ['receive', [Receive::class, 'onReceive']] + ]; + break; + case self::PACKAGE: + $handler = [ + ['packet', [Packet::class, 'onHandler']] + ]; + break; + case self::WEBSOCKET: + $handler = [ + ['handshake', [WebSocket::class, 'onHandshake']], + ['message', [WebSocket::class, 'onMessage']], + ['close', [WebSocket::class, 'onClose']], + ]; + break; + default: + throw new Exception('Unknown server type(' . $config['type'] . ').'); + } + return [$this->server[$config['type']], $config, $handler, $settings]; + } + + +} diff --git a/http-server/ServerManager.php b/http-server/ServerManager.php new file mode 100644 index 00000000..7eb419e5 --- /dev/null +++ b/http-server/ServerManager.php @@ -0,0 +1,186 @@ +set($settings ?? [], $handlers, $config); + static::notice($application, $workerId, $config); + if (property_exists($server, 'pack')) { + $server->pack = $config['message']['pack'] ?? function ($data) { + return $data; + }; + } + if (property_exists($server, 'unpack')) { + $server->unpack = $config['message']['unpack'] ?? function ($data) { + return $data; + }; + } + return $server->start(); + } + + + /** + * @param $application + * @param $config + * @param $category + * @return array + */ + protected static function parameter($application, $config, $category) + { + return [$application, $config['host'], $config['port'], SWOOLE_PROCESS, $category[0]]; + } + + + /** + * @param $process + * @param $application + * @param $pool + * @param $workerId + * @return mixed + */ + protected static function createProcess($process, $application, $pool, $workerId) + { + $process = new $process($application); + $application->debug(sprintf('Worker #%d is running.', $workerId)); + return $process->start($pool->getProcess($workerId)); + } + + + /** + * @param $application + * @param $workerId + * @param $config + */ + protected static function notice($application, $workerId, $config) + { + $application->debug(sprintf('Worker #%d Listener %s::%d is running.', $workerId, $config['host'], $config['port'])); + } + + /** + * @param $server + * @param $settings + * @param \Snowflake\Application $application + * @param array $events + * @param array $config + * @return mixed|void + * @throws NotFindClassException + * @throws ReflectionException + */ + public static function set($server, $settings, $application, $events = [], $config = []) + { + $server->on('start', static::createHandler('start')); + $server->on('workerStop', static::createHandler('workerStop')); + $server->on('workerExit', static::createHandler('workerExit')); + $server->on('workerStart', static::createHandler('workerStart')); + $server->on('workerError', static::createHandler('workerError')); + $server->on('managerStop', static::createHandler('managerStop')); + $server->on('managerStart', static::createHandler('managerStart')); + static::addListener($server, $application, $config); + static::bindCallback($server, $events); + static::addTask($server, $settings); + } + + + /** + * @param $server + * @param $settings + * @throws NotFindClassException + * @throws ReflectionException + */ + protected static function addTask($server, $settings) + { + if (($taskNumber = $settings['task_worker_num'] ?? 0) > 0) { + $server->on('finish', static::createHandler('finish')); + $callback = static::createHandler('task'); + if ($settings['task_enable_coroutine'] ?? false) { + $server->on('task', [$callback, 'onContinueTask']); + } else { + $server->on('task', [$callback, 'onTask']); + } + } + } + + + /** + * @param $server + * @param $application + * @param $config + * @return void + */ + protected static function addListener($server, $application, $config) + { + $grpc = $config['grpc'] ?? []; + if (empty($grpc) || !is_array($grpc)) { + return; + } + $listener = $server->addListener($grpc['host'], $grpc['port'], $grpc['mode']); + $listener->set($grpc['settings'] ?? []); + if (!isset($grpc['receive'])) { + $application->error(sprintf('must add listener %s::%s callback', $grpc['host'], $grpc['port'])); + return; + } + if ($grpc['receive'] instanceof \Closure) { + $grpc['receive'] = \Closure::bind($grpc['receive'], $server); + } + $listener->on('receive', $grpc['receive']); + } + + + /** + * @param $server + * @param $callbacks + */ + protected static function bindCallback($server, $callbacks) + { + if (empty($callbacks) || !is_array($callbacks)) { + return; + } + foreach ($callbacks as $callback) { + $server->on($callback[0], [$server, $callback[1][1]]); + } + } + + + /** + * @param $eventName + * @return array + * @throws NotFindClassException + * @throws ReflectionException + * @throws Exception + */ + protected static function createHandler($eventName) + { + $classPrefix = 'HttpServer\Events\Trigger\On' . ucfirst($eventName); + if (!class_exists($classPrefix)) { + throw new Exception('class not found.'); + } + $class = Snowflake::createObject($classPrefix, [Snowflake::get()]); + return [$class, 'onHandler']; + } + +} diff --git a/http-server/config.php b/http-server/config.php new file mode 100644 index 00000000..4242017c --- /dev/null +++ b/http-server/config.php @@ -0,0 +1,111 @@ + [ + [ + 'type' => Server::HTTP, + 'host' => '127.0.0.1', + 'port' => 9527, + 'settings' => [ + 'worker_num' => 10, + 'enable_coroutine' => 1 + ], + 'events' => [ + Event::SERVER_WORKER_START => function () { + $router = Snowflake::get()->router; + $router->loader(); + }, + ] + ], + [ + 'type' => Server::PACKAGE, + 'host' => '127.0.0.1', + 'port' => 9628, + 'settings' => [ + 'worker_num' => 10, + 'enable_coroutine' => 1 + ], + 'message' => [ + 'pack' => function ($data) { + return \Snowflake\Core\JSON::encode($data); + }, + 'unpack' => function ($data) { + return \Snowflake\Core\JSON::decode($data); + }, + ], + 'events' => [ + ] + ], + [ + 'type' => Server::TCP, + 'host' => '127.0.0.1', + 'port' => 9629, + 'settings' => [ + 'worker_num' => 10, + 'enable_coroutine' => 1 + ], + 'message' => [ + 'pack' => function ($data) { + var_dump($data); + return \Snowflake\Core\JSON::encode($data); + }, + 'unpack' => function ($data) { + return \Snowflake\Core\JSON::decode($data); + }, + ], + 'events' => [ + Event::RECEIVE_CONNECTION => function ($data) { + return 'hello word~'; + } + ] + ], + [ + 'type' => Server::WEBSOCKET, + 'host' => '127.0.0.1', + 'port' => 9530, + 'settings' => [ + 'worker_num' => 10, + 'enable_coroutine' => 1 + ], + 'grpc' => [ + 'host' => '127.0.0.1', + 'port' => 5555, + 'mode' => SWOOLE_SOCK_TCP, + 'receive' => function ($server, int $fd, int $reactorId, string $data) { + $server->push(1, 'success.'); + $server->send($fd, 'success.'); + }, + 'settings' => [] + ], + 'events' => [ + Event::SERVER_WORKER_START => function () { + $websocket = Snowflake::get()->annotation->websocket; +// $websocket->path = $this->socketControllers; + $websocket->namespace = 'App\\Sockets\\'; + $websocket->registration_notes(); + }, + Event::SERVER_HANDSHAKE => function (Request $request, Response $response) { + $this->error($request->fd . ' connect.'); + $response->status(101); + $response->end(); + }, + Event::SERVER_MESSAGE => function (\Swoole\WebSocket\Server $server, Frame $frame) { + $this->error('websocket SERVER_MESSAGE.'); + return $server->push($frame->fd, 'hello word~'); + }, + Event::SERVER_CLOSE => function (int $fd) { + $this->error($fd . ' disconnect.'); + return 'hello word~'; + } + ] + ], + ] +]; diff --git a/p.php b/p.php new file mode 100644 index 00000000..abd21e9e --- /dev/null +++ b/p.php @@ -0,0 +1,75 @@ +setHost('127.0.0.1'); + $client->setPort(9628); + $client->setErrorField('code'); + $client->setErrorMsgField('message'); + var_dump($client->sendTo('', [],SWOOLE_UDP)->getMessage()); + + $client = HttpServer\Client\Client::NewRequest(); + $client->setHost('127.0.0.1'); + $client->setPort(9629); + $client->setErrorField('code'); + $client->setErrorMsgField('message'); + var_dump($client->sendTo('', [],SWOOLE_TCP)->getMessage()); + + $client = HttpServer\Client\Client::NewRequest(); + $client->setHost('127.0.0.1'); + $client->setPort(5555); + $client->setErrorField('code'); + $client->setErrorMsgField('message'); + var_dump($client->sendTo('', [],SWOOLE_SOCK_TCP)->getMessage()); + + $client = HttpServer\Client\Client::NewRequest(); + $client->setHost('127.0.0.1'); + $client->setPort(9527); + $client->setErrorField('code'); + $client->setErrorMsgField('message'); + var_dump($client->send('', [])->getMessage()); +}); +$mail = new \PHPMailer\PHPMailer\PHPMailer(true); + +try { + //Server settings + $mail->SMTPDebug = \PHPMailer\PHPMailer\SMTP::DEBUG_SERVER; // Enable verbose debug output + $mail->isSMTP(); // Send using SMTP + $mail->Host = 'smtp1.example.com'; // Set the SMTP server to send through + $mail->SMTPAuth = true; // Enable SMTP authentication + $mail->Username = 'user@example.com'; // SMTP username + $mail->Password = 'secret'; // SMTP password + $mail->SMTPSecure = \PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS; // Enable TLS encryption; `PHPMailer::ENCRYPTION_SMTPS` encouraged + $mail->Port = 587; // TCP port to connect to, use 465 for `PHPMailer::ENCRYPTION_SMTPS` above + + //Recipients + $mail->setFrom('from@example.com', 'Mailer'); + $mail->addAddress('joe@example.net', 'Joe User'); // Add a recipient + $mail->addAddress('ellen@example.com'); // Name is optional + $mail->addReplyTo('info@example.com', 'Information'); + $mail->addCC('cc@example.com'); + $mail->addBCC('bcc@example.com'); + + // Attachments + $mail->addAttachment('/var/tmp/file.tar.gz'); // Add attachments + $mail->addAttachment('/tmp/image.jpg', 'new.jpg'); // Optional name + + // Content + $mail->isHTML(true); // Set email format to HTML + $mail->Subject = 'Here is the subject'; + $mail->Body = 'This is the HTML message body in bold!'; + $mail->AltBody = 'This is the body in plain text for non-HTML mail clients'; + + $mail->send(); + echo 'Message has been sent'; +} catch (Exception $e) { + echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}"; +} + diff --git a/socket.html b/socket.html new file mode 100644 index 00000000..3e48305f --- /dev/null +++ b/socket.html @@ -0,0 +1,55 @@ + + +
+ +