├── .vscode └── settings.json ├── LICENSE.md ├── README.md ├── README_zh_CN.md ├── composer.json └── src ├── Hprose.php └── Hprose └── RPC ├── Client.php ├── Core ├── Client.php ├── ClientCodec.php ├── ClientContext.php ├── Context.php ├── DefaultClientCodec.php ├── DefaultServiceCodec.php ├── ErrorLevel.php ├── Handler.php ├── IOManager.php ├── InvokeManager.php ├── Method.php ├── MethodManager.php ├── PluginManager.php ├── PluginTrait.php ├── Proxy.php ├── Service.php ├── ServiceCodec.php ├── ServiceContext.php ├── Singleton.php ├── TimeoutException.php ├── Transport.php └── Utils.php ├── Http ├── HttpHandler.php ├── HttpRequest.php ├── HttpResponse.php ├── HttpServer.php └── HttpTransport.php ├── Mock ├── MockAgent.php ├── MockHandler.php ├── MockServer.php └── MockTransport.php ├── Plugins ├── CircuitBreaker │ ├── BreakerException.php │ ├── CircuitBreaker.php │ └── MockService.php ├── Cluster │ ├── Cluster.php │ ├── ClusterConfig.php │ ├── FailfastConfig.php │ ├── FailoverConfig.php │ └── FailtryConfig.php ├── ErrorToException.php ├── ExecuteTimeout.php ├── Forward.php ├── LoadBalance │ ├── NginxRoundRobinLoadBalance.php │ ├── RandomLoadBalance.php │ ├── RoundRobinLoadBalance.php │ ├── WeightedLeastActiveLoadBalance.php │ ├── WeightedLoadBalance.php │ ├── WeightedRandomLoadBalance.php │ └── WeightedRoundRobinLoadBalance.php └── Log.php └── Service.php /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "php.executablePath": "/usr/local/opt/php/bin/php", 3 | "php.validate.executablePath": "/usr/local/opt/php/bin/php", 4 | "phpfmt.php_bin": "/usr/local/opt/php/bin/php", 5 | "phpfmt.psr1": true, 6 | "phpfmt.psr2": false, 7 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2008-2020 http://hprose.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Hprose

2 | 3 | # Hprose 3.0 for PHP 4 | 5 | [![Build Status](https://travis-ci.org/hprose/hprose-php.svg?branch=master)](https://travis-ci.org/hprose/hprose-php) 6 | [![Join the chat at https://gitter.im/hprose/hprose-php](https://img.shields.io/badge/GITTER-join%20chat-green.svg)](https://gitter.im/hprose/hprose-php?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 7 | ![Supported PHP versions: =7.1+](https://img.shields.io/badge/php-7.1+-blue.svg) 8 | [![Packagist](https://img.shields.io/packagist/v/hprose/hprose.svg)](https://packagist.org/packages/hprose/hprose) 9 | [![Packagist Download](https://img.shields.io/packagist/dm/hprose/hprose.svg)](https://packagist.org/packages/hprose/hprose) 10 | [![License](https://img.shields.io/packagist/l/hprose/hprose.svg)](https://packagist.org/packages/hprose/hprose) 11 | 12 | ## Introduction 13 | 14 | *Hprose* is a High Performance Remote Object Service Engine. 15 | 16 | It is a modern, lightweight, cross-language, cross-platform, object-oriented, high performance, remote dynamic communication middleware. It is not only easy to use, but powerful. You just need a little time to learn, then you can use it to easily construct cross language cross platform distributed application system. 17 | 18 | *Hprose* supports many programming languages, for example: 19 | 20 | * AAuto Quicker 21 | * ActionScript 22 | * ASP 23 | * C++ 24 | * Dart 25 | * Delphi/Free Pascal 26 | * dotNET(C#, Visual Basic...) 27 | * Golang 28 | * Java 29 | * JavaScript 30 | * Node.js 31 | * Objective-C 32 | * Perl 33 | * PHP 34 | * Python 35 | * Ruby 36 | * ... 37 | 38 | Through *Hprose*, You can conveniently and efficiently intercommunicate between those programming languages. 39 | 40 | This project is the implementation of Hprose for PHP. -------------------------------------------------------------------------------- /README_zh_CN.md: -------------------------------------------------------------------------------- 1 |

Hprose

2 | 3 | # Hprose 3.0 for PHP 4 | 5 | [![Build Status](https://travis-ci.org/hprose/hprose-php.svg?branch=master)](https://travis-ci.org/hprose/hprose-php) 6 | [![Join the chat at https://gitter.im/hprose/hprose-php](https://img.shields.io/badge/GITTER-join%20chat-green.svg)](https://gitter.im/hprose/hprose-php?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 7 | ![Supported PHP versions: 7.1+](https://img.shields.io/badge/php-7.1+-blue.svg) 8 | [![Packagist](https://img.shields.io/packagist/v/hprose/hprose.svg)](https://packagist.org/packages/hprose/hprose) 9 | [![Packagist Download](https://img.shields.io/packagist/dm/hprose/hprose.svg)](https://packagist.org/packages/hprose/hprose) 10 | [![License](https://img.shields.io/packagist/l/hprose/hprose.svg)](https://packagist.org/packages/hprose/hprose) 11 | 12 | ## 简介 13 | 14 | *Hprose* 是高性能远程对象服务引擎(High Performance Remote Object Service Engine)的缩写。 15 | 16 | 它是一个先进的轻量级的跨语言跨平台面向对象的高性能远程动态通讯中间件。它不仅简单易用,而且功能强大。你只需要稍许的时间去学习,就能用它轻松构建跨语言跨平台的分布式应用系统了。 17 | 18 | *Hprose* 支持众多编程语言,例如: 19 | 20 | * AAuto Quicker 21 | * ActionScript 22 | * ASP 23 | * C++ 24 | * Dart 25 | * Delphi/Free Pascal 26 | * dotNET(C#, Visual Basic...) 27 | * Golang 28 | * Java 29 | * JavaScript 30 | * Node.js 31 | * Objective-C 32 | * Perl 33 | * PHP 34 | * Python 35 | * Ruby 36 | * ... 37 | 38 | 通过 *Hprose*,你就可以在这些语言之间方便高效的实现互通了。 39 | 40 | 本项目是 Hprose 的 PHP 语言版本实现。 -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hprose/hprose", 3 | "type": "library", 4 | "description": "It is a modern, lightweight, cross-language, cross-platform, object-oriented, high performance, remote dynamic communication middleware. It is not only easy to use, but powerful. You just need a little time to learn, then you can use it to easily construct cross language cross platform distributed application system.", 5 | "keywords": [ 6 | "hprose", 7 | "phprpc", 8 | "rpc", 9 | "webservice", 10 | "websocket", 11 | "http", 12 | "ajax", 13 | "json", 14 | "jsonrpc", 15 | "xmlrpc", 16 | "cross-language", 17 | "cross-platform", 18 | "cross-domain", 19 | "html5", 20 | "serialize", 21 | "serialization", 22 | "protocol", 23 | "web", 24 | "service", 25 | "framework", 26 | "library", 27 | "game", 28 | "communication", 29 | "middleware", 30 | "webapi", 31 | "socket", 32 | "tcp", 33 | "async", 34 | "unix", 35 | "future" 36 | ], 37 | "homepage": "http://hprose.com/", 38 | "license": "MIT", 39 | "authors": [ 40 | { 41 | "name": "Ma Bingyao", 42 | "email": "andot@hprose.com", 43 | "homepage": "http://hprose.com", 44 | "role": "Developer" 45 | } 46 | ], 47 | "require": { 48 | "php": ">=7.1.0", 49 | "ext-hprose": ">=1.8.0" 50 | }, 51 | "suggest": {}, 52 | "require-dev": { 53 | "phpunit/phpunit": ">=6.0.0" 54 | }, 55 | "autoload": { 56 | "psr-4": { 57 | "Hprose\\": "src/Hprose" 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/Hprose.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | // Autoload for non-composer applications 16 | spl_autoload_register(function ($className) { 17 | if ((strlen($className) > 7) && (strtolower(substr($className, 0, 7)) === "hprose\\")) { 18 | $file = __DIR__ . DIRECTORY_SEPARATOR . str_replace("\\", DIRECTORY_SEPARATOR, $className) . ".php"; 19 | if (is_file($file)) { 20 | include $file; 21 | return true; 22 | } 23 | } 24 | return false; 25 | }); 26 | -------------------------------------------------------------------------------- /src/Hprose/RPC/Client.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC; 16 | 17 | class_alias('Hprose\\RPC\\Core\\Client', 'Hprose\\RPC\\Client'); 18 | 19 | if (!Client::isRegister('mock')) { 20 | Client::register('mock', 'Hprose\\RPC\\Mock\\MockTransport'); 21 | } 22 | if (!Client::isRegister('http')) { 23 | Client::register('http', 'Hprose\\RPC\\Http\\HttpTransport'); 24 | } 25 | -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/Client.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | use Exception; 18 | 19 | class Client { 20 | private static $transportClasses = []; 21 | private static $schemes = []; 22 | public static function register(string $name, string $transportClass): void { 23 | $schemes = $transportClass::$schemes; 24 | foreach ($schemes as $scheme) { 25 | static::$schemes[$scheme] = $name; 26 | } 27 | static::$transportClasses[$name] = $transportClass; 28 | } 29 | public static function isRegister(string $name): bool { 30 | return isset(static::$transportClasses[$name]); 31 | } 32 | public $requestHeaders = []; 33 | /** @var ClientCodec $codec */ 34 | public $codec; 35 | public $timeout = 30; // second 36 | private $urilist = []; 37 | public function getUris(): array{ 38 | return $this->urilist; 39 | } 40 | public function setUris(array $uris): void { 41 | if (!empty($uris)) { 42 | $this->urilist = $uris; 43 | shuffle($this->urilist); 44 | } 45 | } 46 | private $invokeManager; 47 | private $ioManager; 48 | use PluginTrait; 49 | private $transports = []; 50 | public function __get(string $name): Transport { 51 | return $this->transports[$name]; 52 | } 53 | public function __set(string $name, Transport $transport): void { 54 | $this->transports[$name] = $transport; 55 | } 56 | public function __construct(?array $urilist = null) { 57 | $this->codec = DefaultClientCodec::getInstance(); 58 | $this->invokeManager = new InvokeManager([$this, 'call']); 59 | $this->ioManager = new IOManager([$this, 'transport']); 60 | foreach (static::$transportClasses as $name => $transportClass) { 61 | $this->transports[$name] = new $transportClass(); 62 | } 63 | if (!empty($urilist)) { 64 | $this->urilist = $urilist; 65 | } 66 | } 67 | public function useService(string $namespace = ''): Proxy { 68 | return new Proxy($this, $namespace); 69 | } 70 | public function invoke(string $fullname, array $args = [], $context = null) { 71 | if ($context === null) { 72 | $context = new ClientContext(); 73 | } 74 | if (is_array($context)) { 75 | $context = new ClientContext($context); 76 | } 77 | $context->init($this); 78 | return call_user_func_array($this->invokeManager->handler, [$fullname, &$args, $context]); 79 | } 80 | public function call(string $fullname, array $args, Context $context) { 81 | $request = $this->codec->encode($fullname, $args, $context); 82 | $response = $this->request($request, $context); 83 | return $this->codec->decode($response, $context); 84 | } 85 | public function request(string $request, Context $context): string { 86 | return call_user_func($this->ioManager->handler, $request, $context); 87 | } 88 | public function transport(string $request, Context $context): string { 89 | $uri = $context->uri; 90 | $scheme = parse_url($uri, PHP_URL_SCHEME); 91 | $name = static::$schemes[$scheme]; 92 | if (isset($name)) { 93 | return $this->transports[$name]->transport($request, $context); 94 | } 95 | throw new Exception('The protocol ' . $scheme . ' is not supported.'); 96 | } 97 | public function abort(): void { 98 | foreach ($this->transports as $transport) { 99 | $transport->abort(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/ClientCodec.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | interface ClientCodec { 18 | function encode(string $name, array &$args, ClientContext $context): string; 19 | function decode(string $response, ClientContext $context); 20 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/ClientContext.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | class ClientContext extends Context { 18 | public function __construct(?array $items = null) { 19 | if ($items === null) { 20 | return; 21 | } 22 | $this->items = $items; 23 | } 24 | public function init(Client $client) { 25 | $this->client = $client; 26 | $uris = $client->getUris(); 27 | if (count($uris) > 0) { 28 | $this->uri = $uris[0]; 29 | } 30 | if (!isset($this->timeout)) { 31 | $this->timeout = $client->timeout; 32 | } 33 | $this->requestHeaders = array_merge($this->requestHeaders, $client->requestHeaders); 34 | } 35 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/Context.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | use ArrayAccess; 18 | 19 | class Context implements ArrayAccess { 20 | public $requestHeaders = []; 21 | public $responseHeaders = []; 22 | protected $items = []; 23 | public function __set(string $name, $value): void { 24 | $this->items[$name] = $value; 25 | } 26 | public function __get(string $name) { 27 | return isset($this->items[$name]) ? $this->items[$name] : null; 28 | } 29 | public function __isset(string $name): bool { 30 | return isset($this->items[$name]); 31 | } 32 | public function __unset(string $name): void { 33 | unset($this->items[$name]); 34 | } 35 | public function offsetSet($name, $value) { 36 | $this->items[$name] = $value; 37 | } 38 | public function offsetGet($name) { 39 | return isset($this->items[$name]) ? $this->items[$name] : null; 40 | } 41 | public function offsetExists($name) { 42 | return isset($this->items[$name]); 43 | } 44 | public function offsetUnset($name) { 45 | unset($this->items[$name]); 46 | } 47 | public function getItems(): array{ 48 | return $this->items; 49 | } 50 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/DefaultClientCodec.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | use Exception; 18 | use Hprose\BytesIO; 19 | use Hprose\Reader; 20 | use Hprose\Tags; 21 | use Hprose\Writer; 22 | 23 | class DefaultClientCodec implements ClientCodec { 24 | use Singleton; 25 | public $simple = false; 26 | public function encode(string $name, array &$args, ClientContext $context): string { 27 | $stream = new BytesIO(); 28 | $writer = new Writer($stream, $this->simple); 29 | $headers = $context->requestHeaders; 30 | if ($this->simple) { 31 | $headers['simple'] = true; 32 | } 33 | if (!empty($headers)) { 34 | $stream->write(Tags::TagHeader); 35 | $writer->serialize($headers); 36 | $writer->reset(); 37 | } 38 | $stream->write(Tags::TagCall); 39 | $writer->serialize($name); 40 | if (!empty($args)) { 41 | $writer->reset(); 42 | $writer->serialize($args); 43 | } 44 | $stream->write(Tags::TagEnd); 45 | return $stream->toString(); 46 | } 47 | public function decode(string $response, ClientContext $context) { 48 | $stream = new BytesIO($response); 49 | $reader = new Reader($stream); 50 | $tag = $stream->getc(); 51 | if ($tag === Tags::TagHeader) { 52 | $headers = $reader->unserialize(); 53 | $context->responseHeaders = array_merge($context->responseHeaders, $headers); 54 | $reader->reset(); 55 | $tag = $stream->getc(); 56 | } 57 | switch ($tag) { 58 | case Tags::TagResult: 59 | if (isset($context->responseHeaders['simple'])) { 60 | $reader = new Reader($stream, true); 61 | } 62 | return $reader->unserialize(); 63 | case Tags::TagError: 64 | throw new Exception((string) $reader->unserialize()); 65 | case Tags::TagEnd: 66 | return null; 67 | default: 68 | throw new Exception('Invalid response:\r\n'+$response); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/DefaultServiceCodec.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | use ErrorException; 18 | use Exception; 19 | use Hprose\BytesIO; 20 | use Hprose\Reader; 21 | use Hprose\Tags; 22 | use Hprose\Writer; 23 | use Throwable; 24 | 25 | class DefaultServiceCodec implements ServiceCodec { 26 | use Singleton; 27 | use ErrorLevel; 28 | public $debug = false; 29 | public $simple = false; 30 | public function encode($result, ServiceContext $context): string { 31 | $stream = new BytesIO(); 32 | $writer = new Writer($stream, $this->simple); 33 | $headers = $context->responseHeaders; 34 | if ($this->simple) { 35 | $headers['simple'] = true; 36 | } 37 | if (!empty($headers)) { 38 | $stream->write(Tags::TagHeader); 39 | $writer->serialize($headers); 40 | $writer->reset(); 41 | } 42 | if ($result instanceof ErrorException) { 43 | $stream->write(Tags::TagError); 44 | $writer->serialize( 45 | $this->debug ? 46 | $this->errorLevel[$result->getSeverity()] . ": '" . $result->getMessage() . "'" . " in " . $result->getFile() . ":" . $result->getLine() : 47 | $result->getMessage() 48 | ); 49 | } else if ($result instanceof Throwable) { 50 | $stream->write(Tags::TagError); 51 | $writer->serialize( 52 | $this->debug ? 53 | $result->getMessage() . "\n" . $result->getTraceAsString() : 54 | $result->getMessage() 55 | ); 56 | } else { 57 | $stream->write(Tags::TagResult); 58 | $writer->serialize($result); 59 | } 60 | $stream->write(Tags::TagEnd); 61 | return $stream->toString(); 62 | } 63 | private function decodeMethod(string $fullname, ServiceContext $context): Method { 64 | $service = $context->service; 65 | $method = $service->get($fullname); 66 | if (!isset($method)) { 67 | throw new Exception('Can\'t find this method ' . $fullname . '().'); 68 | } 69 | $context->method = $method; 70 | return $method; 71 | } 72 | private function decodeArguments(Method $method, BytesIO $stream, Reader $reader): array{ 73 | $tag = $stream->getc(); 74 | if ($method->missing) { 75 | if ($tag === Tags::TagList) { 76 | $reader->reset(); 77 | return $reader->readListWithoutTag(); 78 | } 79 | return []; 80 | } 81 | $args = []; 82 | if ($tag === Tags::TagList) { 83 | $reader->reset(); 84 | $args = $reader->readListWithoutTag(); 85 | $count = count($args); 86 | $paramTypes = $method->paramTypes; 87 | for ($i = 0; $i < $count; ++$i) { 88 | if (!empty($paramTypes[$i])) { 89 | switch ($paramTypes[$i]) { 90 | case 'int': 91 | $args[$i] = (int) $args[$i]; 92 | break; 93 | case 'bool': 94 | $args[$i] = (bool) $args[$i]; 95 | break; 96 | case 'float': 97 | $args[$i] = (float) $args[$i]; 98 | break; 99 | case 'string': 100 | $args[$i] = (string) $args[$i]; 101 | break; 102 | case 'array': 103 | case 'iterable': 104 | $args[$i] = (array) $args[$i]; 105 | break; 106 | } 107 | } 108 | } 109 | } 110 | return $args; 111 | } 112 | 113 | public function decode(string $request, ServiceContext $context): array{ 114 | if (empty($request)) { 115 | $this->decodeMethod('~', $context); 116 | return ['~', []]; 117 | } 118 | $stream = new BytesIO($request); 119 | $reader = new Reader($stream); 120 | $tag = $stream->getc(); 121 | if ($tag === Tags::TagHeader) { 122 | $headers = $reader->unserialize(); 123 | $context->requestHeaders = array_merge($context->requestHeaders, $headers); 124 | $reader->reset(); 125 | $tag = $stream->getc(); 126 | } 127 | switch ($tag) { 128 | case Tags::TagCall: 129 | if (isset($context->requestHeaders['simple'])) { 130 | $reader = new Reader($stream, true); 131 | } 132 | $fullname = $reader->readString(); 133 | $args = $this->decodeArguments($this->decodeMethod($fullname, $context), $stream, $reader); 134 | return [$fullname, $args]; 135 | case Tags::TagEnd: 136 | $this->decodeMethod('~', $context); 137 | return ['~', []]; 138 | default: 139 | throw new Exception('Invalid request:\r\n' . $stream->toString()); 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/ErrorLevel.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | trait ErrorLevel { 18 | private $errorLevel = [ 19 | E_ERROR => "Fatal Error", 20 | E_PARSE => "Fatal Error", 21 | E_CORE_ERROR => "Fatal Error", 22 | E_COMPILE_ERROR => "Fatal Error", 23 | E_USER_ERROR => "Fatal Error", 24 | E_WARNING => "Warning", 25 | E_CORE_WARNING => "Warning", 26 | E_USER_WARNING => "Warning", 27 | E_COMPILE_WARNING => "Warning", 28 | E_RECOVERABLE_ERROR => "Warning", 29 | E_NOTICE => "Notice", 30 | E_USER_NOTICE => "Notice", 31 | E_STRICT => "Strict", 32 | E_DEPRECATED => "Deprecated", 33 | E_USER_DEPRECATED => "Deprecated", 34 | ]; 35 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/Handler.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | interface Handler { 18 | function bind($server): void; 19 | } 20 | -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/IOManager.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | class IOManager extends PluginManager { 18 | protected function getNextHandler(callable $handler, callable $next): callable { 19 | return function (string $request, Context $context) use ($handler, $next) { 20 | return call_user_func($handler, $request, $context, $next); 21 | }; 22 | } 23 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/InvokeManager.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | class InvokeManager extends PluginManager { 18 | protected function getNextHandler(callable $handler, callable $next): callable { 19 | return function (string $name, array &$args, Context $context) use ($handler, $next) { 20 | return call_user_func_array($handler, [$name, &$args, $context, $next]); 21 | }; 22 | } 23 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/Method.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | use InvalidArgumentException; 18 | 19 | class Method { 20 | public $missing = false; 21 | public $passContext = false; 22 | public $options = []; 23 | public $callable; 24 | public $fullname; 25 | public $paramTypes = []; 26 | public function __construct(callable $callable, ?string $fullname = null) { 27 | $this->callable = $callable; 28 | $reflection = Utils::getReflectionCallable($callable); 29 | if (empty($fullname)) { 30 | $fullname = $reflection->getShortName(); 31 | if (empty($fullname)) { 32 | throw new InvalidArgumentException('fullname must not be empty'); 33 | } 34 | } 35 | $this->fullname = $fullname; 36 | $params = $reflection->getParameters(); 37 | foreach ($params as $param) { 38 | $type = $param->getType(); 39 | if ($type === null) { 40 | $this->paramTypes[] = null; 41 | } else { 42 | $this->paramTypes[] = $type->getName(); 43 | } 44 | } 45 | $lastParamType = end($this->paramTypes); 46 | if ($lastParamType === 'Hprose\\RPC\\Core\\Context' || $lastParamType === 'Hprose\\RPC\\Core\\ServiceContext') { 47 | $this->passContext = true; 48 | array_pop($this->paramTypes); 49 | } else { 50 | reset($this->paramTypes); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/MethodManager.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | use ReflectionMethod; 18 | 19 | class MethodManager { 20 | private static $magicMethods = array( 21 | "__construct", 22 | "__destruct", 23 | "__call", 24 | "__callStatic", 25 | "__get", 26 | "__set", 27 | "__isset", 28 | "__unset", 29 | "__sleep", 30 | "__wakeup", 31 | "__toString", 32 | "__invoke", 33 | "__set_state", 34 | "__clone", 35 | ); 36 | private $methods = []; 37 | private $names = []; 38 | public function getNames(): array{ 39 | return $this->names; 40 | } 41 | public function get(string $fullname): Method { 42 | return $this->methods[strtolower($fullname)] ?? $this->methods['*']; 43 | } 44 | public function remove(string $fullname): void { 45 | usset($this->methods[strtolower($fullname)]); 46 | $this->names = array_values(array_diff($this->names, [$fullname])); 47 | } 48 | public function add(Method $method): void { 49 | $fullname = $method->fullname; 50 | $this->methods[strtolower($fullname)] = $method; 51 | if (!in_array($fullname, $this->names, true)) { 52 | $this->names[] = $fullname; 53 | } 54 | } 55 | public function addMissingMethod(callable $callable): void { 56 | $method = new Method($callable, '*'); 57 | $method->missing = true; 58 | if (Utils::getNumberOfParameters($callable) === 3) { 59 | $method->passContext = true; 60 | } 61 | $this->add($method); 62 | } 63 | public function addCallable(callable $callable, ?string $fullname = null): void { 64 | $this->add(new Method($callable, $fullname)); 65 | } 66 | public function addInstanceMethods(object $object, ?string $namespace = null): void { 67 | $methods = array_diff(get_class_methods($object), self::$magicMethods); 68 | foreach ($methods as $name) { 69 | $method = new ReflectionMethod($object, $name); 70 | if ($method->isPublic() && 71 | !$method->isStatic() && 72 | !$method->isConstructor() && 73 | !$method->isDestructor() && 74 | !$method->isAbstract()) { 75 | $this->addCallable([$object, $name], empty($namespace) ? $name : $namespace . '_' . $name); 76 | } 77 | } 78 | } 79 | public function addStaticMethods(string $class, ?string $namespace = null): void { 80 | $methods = array_diff(get_class_methods($class), self::$magicMethods); 81 | foreach ($methods as $name) { 82 | $method = new ReflectionMethod($class, $name); 83 | if ($method->isPublic() && 84 | $method->isStatic() && 85 | !$method->isAbstract()) { 86 | $this->addCallable([$class, $name], empty($namespace) ? $name : $namespace . '_' . $name); 87 | } 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/PluginManager.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | abstract class PluginManager { 18 | private $handlers = []; 19 | private $defaultHandler; 20 | public $handler; 21 | public function __construct($defaultHandler) { 22 | $this->defaultHandler = $defaultHandler; 23 | $this->handler = $defaultHandler; 24 | } 25 | 26 | abstract protected function getNextHandler(callable $handler, callable $next): callable ; 27 | 28 | private function rebuildHandler(): void { 29 | $handlers = $this->handlers; 30 | $next = $this->defaultHandler; 31 | $n = count($handlers); 32 | foreach ($handlers as $handler) { 33 | $next = $this->getNextHandler($handler, $next); 34 | } 35 | $this->handler = $next; 36 | } 37 | 38 | public function use (callable ...$handlers): void { 39 | array_push($this->handlers, ...$handlers); 40 | $this->rebuildHandler(); 41 | } 42 | 43 | public function unuse(callable ...$handlers): void { 44 | $rebuild = false; 45 | foreach ($handlers as $handler) { 46 | $index = array_search($handler, $this->handlers); 47 | if ($index !== false) { 48 | array_splice($this->handlers, $index, 1); 49 | $rebuild = true; 50 | } 51 | } 52 | if ($rebuild) { 53 | $this->rebuildHandler(); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/PluginTrait.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | trait PluginTrait { 18 | private function sortHandlers(array $handlers): array{ 19 | $invokeHandlers = []; 20 | $ioHandlers = []; 21 | foreach ($handlers as $handler) { 22 | switch (Utils::getNumberOfParameters($handler)) { 23 | case 4: 24 | $invokeHandlers[] = $handler; 25 | break; 26 | case 3: 27 | $ioHandlers[] = $handler; 28 | break; 29 | default: 30 | throw new InvalidArgumentException('Invalid parameter type'); 31 | } 32 | } 33 | return [$invokeHandlers, $ioHandlers]; 34 | } 35 | public function use (callable ...$handlers): self { 36 | [$invokeHandlers, $ioHandlers] = $this->sortHandlers($handlers); 37 | if (count($invokeHandlers) > 0) { 38 | $this->invokeManager->use(...$invokeHandlers); 39 | } 40 | if (count($ioHandlers) > 0) { 41 | $this->ioManager->use(...$ioHandlers); 42 | } 43 | return $this; 44 | } 45 | public function unuse(callable ...$handlers): self { 46 | [$invokeHandlers, $ioHandlers] = $this->sortHandlers($handlers); 47 | if (count($invokeHandlers) > 0) { 48 | $this->invokeManager->unuse(...$invokeHandlers); 49 | } 50 | if (count($ioHandlers) > 0) { 51 | $this->ioManager->unuse(...$ioHandlers); 52 | } 53 | return $this; 54 | } 55 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/Proxy.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | class Proxy { 18 | private $client; 19 | private $name; 20 | private $namespace; 21 | private $methodCache = []; 22 | public function __construct(Client $client, string $namespace) { 23 | $this->client = $client; 24 | $this->name = $namespace; 25 | if (empty($namespace)) { 26 | $this->namespace = ''; 27 | } else { 28 | $this->namespace = $namespace . '_'; 29 | } 30 | } 31 | private function call(string $name, array &$args) { 32 | $context = null; 33 | $n = count($args); 34 | if ($n > 0) { 35 | if ($args[$n - 1] instanceof ClientContext) { 36 | $context = array_pop($args); 37 | } 38 | } 39 | return $this->client->invoke($name, $args, $context); 40 | } 41 | public function __invoke(...$args) { 42 | if (empty($this->name)) { 43 | return; 44 | } 45 | return $this->call($this->name, $args); 46 | } 47 | public function __call($name, array $args) { 48 | return $this->call($this->namespace . $name, $args); 49 | } 50 | public function __get($name) { 51 | if (isset($this->methodCache[$name])) { 52 | return $this->methodCache[$name]; 53 | } 54 | $method = new Proxy($this->client, $this->namespace . $name); 55 | $this->methodCache[$name] = $method; 56 | return $method; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/Service.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | use InvalidArgumentException; 18 | use Throwable; 19 | 20 | class Service { 21 | private static $handlerClasses = []; 22 | private static $serverTypes = []; 23 | static function register(string $name, string $handlerClass): void { 24 | $types = $handlerClass::$serverTypes; 25 | foreach ($types as $type) { 26 | if (isset(static::$serverTypes[$type])) { 27 | static::$serverTypes[$type][] = $name; 28 | } else { 29 | static::$serverTypes[$type] = [$name]; 30 | } 31 | } 32 | static::$handlerClasses[$name] = $handlerClass; 33 | } 34 | static function isRegister(string $name): bool { 35 | return isset(static::$handlerClasses[$name]); 36 | } 37 | /** @var ServiceCodec $codec */ 38 | public $codec; 39 | public $maxRequestLength = 0x7FFFFFFFF; 40 | public $options = []; 41 | private $invokeManager; 42 | private $ioManager; 43 | use PluginTrait; 44 | private $methodManager; 45 | private $handlers = []; 46 | public function __get(string $name): Handler { 47 | return $this->handlers[$name]; 48 | } 49 | public function __set(string $name, Handler $handler): void { 50 | $this->handlers[$name] = $handler; 51 | } 52 | public function getNames(): array{ 53 | return $this->methodManager->getNames(); 54 | } 55 | public function __construct() { 56 | $this->codec = DefaultServiceCodec::getInstance(); 57 | $this->invokeManager = new InvokeManager([$this, 'execute']); 58 | $this->ioManager = new IOManager([$this, 'process']); 59 | foreach (static::$handlerClasses as $name => $handlerClass) { 60 | $this->handlers[$name] = new $handlerClass($this); 61 | } 62 | $this->methodManager = new MethodManager(); 63 | $this->addCallable([$this->methodManager, 'getNames'], '~'); 64 | } 65 | public function createContext(): ServiceContext { 66 | return new ServiceContext($this); 67 | } 68 | public function bind($server, ?string $name = null): void { 69 | $type = get_class($server); 70 | if (isset(self::$serverTypes[$type])) { 71 | $names = self::$serverTypes[$type]; 72 | foreach ($names as $n) { 73 | if (empty($name) || $name === $n) { 74 | $this->handlers[$n]->bind($server); 75 | } 76 | } 77 | } else { 78 | throw new InvalidArgumentException('This type server is not supported.'); 79 | } 80 | } 81 | public function handle(string $request, Context $context): string { 82 | try { 83 | $response = call_user_func($this->ioManager->handler, $request, $context); 84 | if ($response == null) { 85 | $response = $this->codec->encode(null, $context); 86 | } 87 | return $response; 88 | } catch (Throwable $e) { 89 | return $this->codec->encode($e, $context); 90 | } 91 | } 92 | public function process(string $request, Context $context): string { 93 | [$name, $args] = $this->codec->decode($request, $context); 94 | $result = call_user_func_array($this->invokeManager->handler, [$name, &$args, $context]); 95 | return $this->codec->encode($result, $context); 96 | } 97 | public function execute(string $fullname, array &$args, Context $context) { 98 | $method = $context->method; 99 | if ($method->missing) { 100 | if ($method->passContext) { 101 | return call_user_func_array($method->callable, [$fullname, &$args, $context]); 102 | } 103 | return call_user_func_array($method->callable, [$fullname, &$args]); 104 | } 105 | if ($method->passContext) { 106 | $args[] = $context; 107 | } 108 | return call_user_func_array($method->callable, $args); 109 | } 110 | public function get(string $fullname): Method { 111 | return $this->methodManager->get($fullname); 112 | } 113 | public function add(Method $method): self { 114 | $this->methodManager->add($method); 115 | return $this; 116 | } 117 | public function remove(Method $method): self { 118 | $this->methodManager->remove($method); 119 | return $this; 120 | } 121 | public function addMissingMethod(callable $callable): self { 122 | $this->methodManager->addMissingMethod($callable); 123 | return $this; 124 | } 125 | public function addCallable(callable $callable, ?string $fullname = null): self { 126 | $this->methodManager->addCallable($callable, $fullname); 127 | return $this; 128 | } 129 | public function addInstanceMethods(object $object, ?string $namespace = null): self { 130 | $this->methodManager->addInstanceMethods($object, $namespace); 131 | return $this; 132 | } 133 | public function addStaticMethods(string $class, ?string $namespace = null): self { 134 | $this->methodManager->addStaticMethods($class, $namespace); 135 | return $this; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/ServiceCodec.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | interface ServiceCodec { 18 | function encode($result, ServiceContext $context): string; 19 | function decode(string $request, ServiceContext $context): array; 20 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/ServiceContext.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | class ServiceContext extends Context { 18 | public $service; 19 | public function __construct(Service $service) { 20 | $this->service = $service; 21 | } 22 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/Singleton.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | trait Singleton { 18 | private static $instance = null; 19 | public static function getInstance() { 20 | if (null === self::$instance) { 21 | self::$instance = new static(); 22 | } 23 | return self::$instance; 24 | } 25 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/TimeoutException.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | use Exception; 18 | 19 | class TimeoutException extends Exception {} -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/Transport.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | interface Transport { 18 | function transport(string $request, Context $context): string; 19 | function abort(): void; 20 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Core/Utils.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Core; 16 | 17 | use ReflectionFunction; 18 | use ReflectionFunctionAbstract; 19 | use ReflectionMethod; 20 | 21 | class Utils { 22 | public static function getReflectionCallable(callable $callable): ReflectionFunctionAbstract { 23 | if (is_array($callable)) { 24 | return new ReflectionMethod($callable[0], $callable[1]); 25 | } 26 | if (is_string($callable) && strpos($callable, '::') !== false) { 27 | return new ReflectionMethod($callable); 28 | } 29 | return new ReflectionFunction($callable); 30 | } 31 | public static function getNumberOfParameters(callable $callable): int { 32 | return static::getReflectionCallable($callable)->getNumberOfParameters(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Hprose/RPC/Http/HttpHandler.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Http; 16 | 17 | use Hprose\RPC\Core\Handler; 18 | use Hprose\RPC\Core\Service; 19 | use Hprose\RPC\Core\ServiceContext; 20 | 21 | class HttpHandler implements Handler { 22 | public static $serverTypes = ['Hprose\\RPC\\Http\\HttpServer']; 23 | public $service; 24 | public $crossDomain = false; 25 | public $p3p = false; 26 | public $get = true; 27 | public $httpHeaders = []; 28 | private $origins = []; 29 | public function __construct(Service $service) { 30 | $this->service = $service; 31 | } 32 | public function bind($server): void { 33 | $server->onRequest([$this, 'handler']); 34 | } 35 | private function header($request, $response): void { 36 | $response->headers['Content-Type'] = 'text/plain'; 37 | if ($this->p3p) { 38 | $response->headers['P3P'] = 39 | 'CP="CAO DSP COR CUR ADM DEV TAI PSA PSD ' . 40 | 'IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi ' . 41 | 'UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV ' . 42 | 'INT DEM CNT STA POL HEA PRE GOV"'; 43 | } 44 | if ($this->crossDomain) { 45 | $origin = $request->headers['Origin'] ?? 'null'; 46 | if ($origin !== 'null') { 47 | if (count($this->origins) === 0 || isset($this->origins[strtolower($origin)])) { 48 | $response->headers['Access-Control-Allow-Origin'] = $origin; 49 | $response->headers['Access-Control-Allow-Credentials'] = 'true'; 50 | } 51 | } else { 52 | $response->headers['Access-Control-Allow-Origin'] = '*'; 53 | } 54 | } 55 | if (!empty($httpHeaders)) { 56 | foreach ($httpHeaders as $name => $value) { 57 | $response->headers[$name] = $value; 58 | } 59 | } 60 | } 61 | public function handler($request, $response): void { 62 | $this->header($request, $response); 63 | $body = $request->body(); 64 | if (strlen($body) > $this->service->maxRequestLength) { 65 | $response->end(413); 66 | return; 67 | } 68 | if ($request->method === 'GET') { 69 | if (!$this->get) { 70 | $response->end(403); 71 | return; 72 | } 73 | } 74 | $context = new ServiceContext($this->service); 75 | $context->remoteAddress = [ 76 | 'family' => 'tcp', 77 | 'address' => $request->address, 78 | 'port' => $request->port, 79 | ]; 80 | $context->localAddress = [ 81 | 'family' => 'tcp', 82 | 'address' => $request->server->address, 83 | 'port' => $request->server->port, 84 | ]; 85 | $context->handler = $this; 86 | $data = $this->service->handle($body, $context); 87 | $response->end(200, $data); 88 | } 89 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Http/HttpRequest.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Http; 16 | 17 | class HttpRequest { 18 | public $server; 19 | public $headers = []; 20 | public $method; 21 | public $address; 22 | public $port; 23 | public $path; 24 | public function __construct($server) { 25 | $this->server = $server; 26 | $this->headers = $this->getHeaders(); 27 | $this->method = $_SERVER['REQUEST_METHOD'] ?? ''; 28 | $this->address = $_SERVER['REMOTE_ADDR'] ?? ''; 29 | $this->port = $_SERVER['REMOTE_PORT'] ?? 0; 30 | $this->path = $_SERVER['REQUEST_URI'] ?? '/'; 31 | } 32 | public function body(): string { 33 | return file_get_contents("php://input"); 34 | } 35 | private function getHeaders(): array{ 36 | $headers = []; 37 | foreach ($_SERVER as $name => $value) { 38 | if (substr($name, 0, 5) === 'HTTP_') { 39 | $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))); 40 | $headers[$name] = $value; 41 | } 42 | } 43 | if (isset($_SERVER['CONTENT_TYPE'])) { 44 | $headers['Content-Type'] = $_SERVER['CONTENT_TYPE']; 45 | } 46 | if (isset($_SERVER['CONTENT_LENGTH'])) { 47 | $headers['Content-Length'] = $_SERVER['CONTENT_LENGTH']; 48 | } 49 | if (isset($_SERVER['CONTENT_MD5'])) { 50 | $headers['Content-Md5'] = $_SERVER['CONTENT_MD5']; 51 | } 52 | if (!isset($headers['Authorization'])) { 53 | if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { 54 | $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; 55 | } elseif (isset($_SERVER['PHP_AUTH_USER'])) { 56 | $basic_pass = $_SERVER['PHP_AUTH_PW'] ?? ''; 57 | $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass); 58 | } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { 59 | $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST']; 60 | } 61 | } 62 | return $headers; 63 | } 64 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Http/HttpResponse.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Http; 16 | 17 | class HttpResponse { 18 | public $headers = []; 19 | public function end(int $code = 200, string $data = ''): void { 20 | http_response_code($code); 21 | foreach ($this->headers as $name => $value) { 22 | header("$name: $value"); 23 | } 24 | echo $data; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Http/HttpServer.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Http; 16 | 17 | use Hprose\RPC\Core\Singleton; 18 | 19 | class HttpServer { 20 | use Singleton; 21 | private $handler; 22 | public $address; 23 | public $port; 24 | public function __construct() { 25 | $this->address = $_SERVER['SERVER_ADDR'] ?? ''; 26 | $this->port = $_SERVER['SERVER_PORT'] ?? 80; 27 | } 28 | public function onRequest(callable $handler): void { 29 | $this->handler = $handler; 30 | } 31 | public function listen(): void { 32 | call_user_func($this->handler, new HttpRequest($this), new HttpResponse()); 33 | } 34 | public function close(): void {} 35 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Http/HttpTransport.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Http; 16 | 17 | use Exception; 18 | use Hprose\RPC\Core\Context; 19 | use Hprose\RPC\Core\Transport; 20 | 21 | class HttpTransport implements Transport { 22 | public static $schemes = ['http', 'https']; 23 | private $curl; 24 | public $httpRequestHeaders = []; 25 | public function __construct() { 26 | $this->curl = curl_init(); 27 | $this->setKeepAlive(300); 28 | } 29 | public function __destruct() { 30 | curl_close($this->curl); 31 | } 32 | public function setKeepAlive(int $timeout, int $max = 0): void { 33 | if ($timeout > 0) { 34 | $this->httpRequestHeaders['Connection'] = 'keep-alive'; 35 | $this->httpRequestHeaders['Keep-Alive'] = 'timeout=' . $timeout; 36 | if ($max > 0) { 37 | $this->httpRequestHeaders['Keep-Alive'] .= ', max=' . $max; 38 | } 39 | curl_setopt($this->curl, CURLOPT_FRESH_CONNECT, false); 40 | curl_setopt($this->curl, CURLOPT_FORBID_REUSE, false); 41 | } else { 42 | $this->httpRequestHeaders['Connection'] = 'close'; 43 | unset($this->httpRequestHeaders['Keep-Alive']); 44 | curl_setopt($this->curl, CURLOPT_FRESH_CONNECT, true); 45 | curl_setopt($this->curl, CURLOPT_FORBID_REUSE, true); 46 | } 47 | } 48 | public function setOptions(array $options): void { 49 | curl_setopt_array($this->curl, $options); 50 | } 51 | public function transport(string $request, Context $context): string { 52 | $timeout = $context->timeout; 53 | if ($timeout > 0) { 54 | curl_setopt($this->curl, CURLOPT_TIMEOUT, $timeout); 55 | } else { 56 | curl_setopt($this->curl, CURLOPT_TIMEOUT, 0); 57 | } 58 | curl_setopt($this->curl, CURLOPT_URL, $context->uri); 59 | $headers = []; 60 | foreach ($this->httpRequestHeaders as $name => $value) { 61 | $headers[] = $name . ': ' . $value; 62 | } 63 | if (is_array($context->httpRequestHeaders)) { 64 | foreach ($context->httpRequestHeaders as $name => $value) { 65 | $headers[] = $name . ': ' . $value; 66 | } 67 | } 68 | curl_setopt($this->curl, CURLOPT_HTTPHEADER, $headers); 69 | if (!ini_get('safe_mode')) { 70 | curl_setopt($this->curl, CURLOPT_FOLLOWLOCATION, true); 71 | } 72 | curl_setopt($this->curl, CURLOPT_POSTFIELDS, $request); 73 | curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true); 74 | curl_setopt($this->curl, CURLOPT_HEADER, true); 75 | $response = curl_exec($this->curl); 76 | $errno = curl_errno($this->curl); 77 | if ($errno) { 78 | throw new Exception($errno . ': ' . curl_error($this->curl)); 79 | } 80 | do { 81 | [$header, $response] = explode("\r\n\r\n", $response, 2); 82 | $headers = explode("\r\n", $header); 83 | $firstline = array_shift($headers); 84 | $matches = []; 85 | if (preg_match('@^HTTP/[0-9]+(\.?[0-9]+)?\s([0-9]{3})\s(.*)@', $firstline, $matches)) { 86 | $code = $matches[2]; 87 | $status = trim($matches[3]); 88 | } else { 89 | $code = "500"; 90 | $status = "Internal Server Error"; 91 | } 92 | } while (in_array(substr($code, 0, 1), ["1", "3"])); 93 | $context->httpStatusCode = $code; 94 | $context->httpStatusText = $status; 95 | $httpResponseHeaders = []; 96 | foreach ($headers as $headerline) { 97 | $pair = explode(':', $headerline, 2); 98 | $name = trim($pair[0]); 99 | $value = (count($pair) > 1) ? trim($pair[1]) : ''; 100 | if (array_key_exists($name, $httpResponseHeaders)) { 101 | if (is_array($httpResponseHeaders[$name])) { 102 | $httpResponseHeaders[$name][] = $value; 103 | } else { 104 | $httpResponseHeaders[$name] = [$httpResponseHeaders[$name], $value]; 105 | } 106 | } else { 107 | $httpResponseHeaders[$name] = $value; 108 | } 109 | } 110 | $context->httpResponseHeaders = $httpResponseHeaders; 111 | if ($code != '200') { 112 | throw new Exception($code . ": " . $status . "\r\n\r\n" . $response); 113 | } 114 | return $response; 115 | } 116 | public function abort(): void { 117 | curl_close($this->curl); 118 | $this->curl = curl_init(); 119 | $this->setKeepAlive(300); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Hprose/RPC/Mock/MockAgent.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Mock; 16 | 17 | use Exception; 18 | 19 | class MockAgent { 20 | private static $handlers = []; 21 | public static function register(string $address, callable $handler): void { 22 | self::$handlers[$address] = $handler; 23 | } 24 | public static function cancel(string $address): void { 25 | unset(self::$handlers[$address]); 26 | } 27 | public static function handler(string $address, string $request): string { 28 | $handler = self::$handlers[$address]; 29 | if (isset($handler)) { 30 | return call_user_func($handler, $address, $request); 31 | } 32 | throw new Exception('Server is stopped'); 33 | } 34 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Mock/MockHandler.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Mock; 16 | 17 | use Exception; 18 | use Hprose\RPC\Core\Handler; 19 | use Hprose\RPC\Core\Service; 20 | use Hprose\RPC\Core\ServiceContext; 21 | 22 | class MockHandler implements Handler { 23 | public static $serverTypes = ['Hprose\\RPC\\Mock\\MockServer']; 24 | public $service; 25 | public function __construct(Service $service) { 26 | $this->service = $service; 27 | } 28 | public function bind($server): void { 29 | MockAgent::register($server->address, [$this, 'handler']); 30 | } 31 | public function handler(string $address, string $request): string { 32 | if (strlen($request) > $this->service->maxRequestLength) { 33 | throw new Exception('Request entity too large'); 34 | } 35 | $context = new ServiceContext($this->service); 36 | $addressInfo = [ 37 | 'family' => 'mock', 38 | 'address' => $address, 39 | 'port' => 0, 40 | ]; 41 | $context->remoteAddress = $addressInfo; 42 | $context->localAddress = $addressInfo; 43 | $context->handler = $this; 44 | return $this->service->handle($request, $context); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Hprose/RPC/Mock/MockServer.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Mock; 16 | 17 | class MockServer { 18 | public $address; 19 | public function __construct(string $address) { 20 | $this->address = $address; 21 | } 22 | public function close() { 23 | MockAgent::cancel($this->address); 24 | } 25 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Mock/MockTransport.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Mock; 16 | 17 | use Hprose\RPC\Core\Context; 18 | use Hprose\RPC\Core\TimeoutException; 19 | use Hprose\RPC\Core\Transport; 20 | 21 | class MockTransport implements Transport { 22 | public static $schemes = ['mock']; 23 | public function transport(string $request, Context $context): string { 24 | $uri = parse_url($context->uri); 25 | $timeout = $context->timeout; 26 | if ($timeout > 0) { 27 | $async = pcntl_async_signals(); 28 | try { 29 | pcntl_async_signals(true); 30 | pcntl_signal(SIGALRM, function () { 31 | throw new TimeoutException('timeout'); 32 | }); 33 | pcntl_alarm($timeout); 34 | return MockAgent::handler($uri['host'], $request); 35 | } finally { 36 | pcntl_alarm(0); 37 | pcntl_async_signals($async); 38 | } 39 | } else { 40 | return MockAgent::handler($uri['host'], $request); 41 | } 42 | } 43 | public function abort(): void {} 44 | } 45 | -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/CircuitBreaker/BreakerException.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins\CircuitBreaker; 16 | 17 | use Exception; 18 | 19 | class BreakerException extends Exception {} -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/CircuitBreaker/CircuitBreaker.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins\CircuitBreaker; 16 | 17 | use Hprose\RPC\Core\Context; 18 | use Throwable; 19 | 20 | class CircuitBreaker { 21 | private $lastFailTime = 0; 22 | private $failCount = 0; 23 | public $threshold; 24 | public $recoverTime; 25 | public $mockService; 26 | public function __construct(int $threshold = 5, int $recoverTime = 30, ?MockService $mockService = null) { 27 | $this->threshold = $threshold; 28 | $this->recoverTime = $recoverTime; 29 | $this->mockService = $mockService; 30 | } 31 | public function ioHandler(string $request, Context $context, callable $next): string { 32 | if ($this->failCount > $this->threshold) { 33 | $interval = time() - $this->lastFailTime; 34 | if ($interval < $this->recoverTime) { 35 | throw new BreakerException('Service breaked'); 36 | } 37 | $this->failCount = $this->threshold >> 1; 38 | } 39 | try { 40 | $response = $next($request, $context); 41 | $this->failCount = 0; 42 | return $response; 43 | } catch (Throwable $e) { 44 | ++$this->failCount; 45 | $this->lastFailTime = time(); 46 | throw $e; 47 | } 48 | } 49 | public function invokeHandler(string $name, array &$args, Context $context, callable $next) { 50 | if ($this->mockService === null) { 51 | return $next($name, $args, $context); 52 | } 53 | try { 54 | return $next($name, $args, $context); 55 | } catch (BreakerException $e) { 56 | return $this->mockService->invoke($name, $args, $context); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/CircuitBreaker/MockService.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins\CircuitBreaker; 16 | 17 | use Hprose\RPC\Core\Context; 18 | 19 | interface MockService { 20 | function invoke(string $name, array &$args, Context $context); 21 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/Cluster/Cluster.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins\Cluster; 16 | use Throwable; 17 | 18 | class Cluster { 19 | public $config; 20 | public function __construct(ClusterConfig $config = null) { 21 | if ($config === null) { 22 | $this->config = FailoverConfig::getInstance(); 23 | } else { 24 | $this->config = $config; 25 | } 26 | } 27 | public function handler(string $request, Context $context, callable $next): string { 28 | $config = $this->config; 29 | try { 30 | $response = $next($request, $context); 31 | if ($config->onSuccess !== null) { 32 | $config->onSuccess($context); 33 | } 34 | return $response; 35 | } catch (Throwable $e) { 36 | if ($config->onFailure !== null) { 37 | $config->onFailure($context); 38 | } 39 | if ($config->onRetry !== null) { 40 | $idempotent = ($context->idempotent === null) ? $config->idempotent : $context->idempotent; 41 | $retry = ($context->retry === null) ? $config->retry : $context->retry; 42 | if (!isset($context->retried)) { 43 | $context->retried = 0; 44 | } 45 | if ($idempotent && $context->retried < $retry) { 46 | $interval = $config->onRetry($context); 47 | if ($interval > 0) { 48 | $this->sleep($interval); 49 | } 50 | return $this->handler($request, $context, $next); 51 | } 52 | } 53 | throw $e; 54 | } 55 | } 56 | protected function sleep(float $interval): void { 57 | $seconds = (int) $interval; 58 | $nanoseconds = (int) (($interval - $seconds) * 1000000000); 59 | time_nanosleep($seconds, $nanoseconds); 60 | } 61 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/Cluster/ClusterConfig.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins\Cluster; 16 | 17 | class ClusterConfig { 18 | public $retry = 10; 19 | public $idempotent = false; 20 | // function onSuccess(Context context): void 21 | public $onSuccess; 22 | // function onFailure(Context context): void 23 | public $onFailure; 24 | // function onRetry(Context context): int 25 | public $onRetry; 26 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/Cluster/FailfastConfig.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins\Cluster; 16 | 17 | class FailfastConfig extends ClusterConfig { 18 | public $retry = 0; 19 | public function __construct(callable $onFailure) { 20 | $this->onFailure = $onFailure; 21 | } 22 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/Cluster/FailoverConfig.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins\Cluster; 16 | 17 | class FailoverConfig extends ClusterConfig { 18 | use Singleton; 19 | public function __construct(int $retry = 10, float $minInterval = 0.5, float $maxInterval = 5.0) { 20 | $this->retry = $retry; 21 | $index = 0; 22 | $this->onFailure = function (Context $context): void { 23 | $uris = $context->client->getUris(); 24 | $n = count($uris); 25 | if ($n > 1) { 26 | $index = ($index + 1) % $n; 27 | $context->uri = $uris[$index]; 28 | } 29 | }; 30 | $this->onRetry = function (Context $context): float { 31 | $context->retried++; 32 | $interval = ($context->retried - count($context->client->getUris())) * $minInterval; 33 | if ($interval > $maxInterval) { 34 | $interval = $maxInterval; 35 | } 36 | return $interval; 37 | }; 38 | } 39 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/Cluster/FailtryConfig.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins\Cluster; 16 | 17 | class FailtryConfig extends ClusterConfig { 18 | use Singleton; 19 | public function __construct(int $retry = 10, float $minInterval = 0.5, float $maxInterval = 5.0) { 20 | $this->retry = $retry; 21 | $this->onRetry = function (Context $context): float { 22 | $interval = (++$context->retried) * $minInterval; 23 | if ($interval > $maxInterval) { 24 | $interval = $maxInterval; 25 | } 26 | return $interval; 27 | }; 28 | } 29 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/ErrorToException.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins; 16 | 17 | use ErrorException; 18 | use Hprose\RPC\Core\Context; 19 | 20 | class ErrorToException { 21 | public $error_types; 22 | public function __construct(int $error_types = E_ALL | E_STRICT) { 23 | $this->error_types = $error_types; 24 | } 25 | public function handler(string $request, Context $context, callable $next): string { 26 | $error_handler = set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) { 27 | throw new ErrorException($errstr, 0, $errno, $errfile, $errline); 28 | }); 29 | try { 30 | return $next($request, $context); 31 | } finally { 32 | set_error_handler($error_handler); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/ExecuteTimeout.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins; 16 | 17 | use Hprose\RPC\Core\Context; 18 | use Hprose\RPC\Core\TimeoutException; 19 | 20 | class ExecuteTimeout { 21 | public $timeout; // second 22 | public function __construct(int $timeout = 30) { 23 | $this->timeout = $timeout; 24 | } 25 | public function handler(string $name, array &$args, Context $context, callable $next) { 26 | $timeout = $context->method->options['timeout'] ?? $this->timeout; 27 | $timeout = $timeout; 28 | if ($timeout > 0) { 29 | $async = pcntl_async_signals(); 30 | try { 31 | pcntl_async_signals(true); 32 | pcntl_signal(SIGALRM, function () { 33 | throw new TimeoutException('timeout'); 34 | }); 35 | pcntl_alarm($timeout); 36 | return $next($name, $args, $context); 37 | } finally { 38 | pcntl_alarm(0); 39 | pcntl_async_signals($async); 40 | } 41 | } else { 42 | return $next($name, $args, $context); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/Forward.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins; 16 | 17 | use Hprose\RPC\Client; 18 | use Hprose\RPC\Core\ClientContext; 19 | use Hprose\RPC\Core\Context; 20 | 21 | class Forward { 22 | public $client; 23 | public $timeout = null; 24 | public function __construct(?array $urilist = null) { 25 | $this->client = new Client($urilist); 26 | } 27 | public function ioHandler(string $request, Context $context, callable $next): string { 28 | $clientContext = new ClientContext(); 29 | $clientContext->timeout = $this->timeout; 30 | $clientContext->init($this->client); 31 | return $this->client->request($request, $clientContext); 32 | } 33 | public function invokeHandler(string $name, array &$args, Context $context, callable $next) { 34 | $clientContext = new ClientContext(); 35 | $clientContext->timeout = $this->timeout; 36 | $clientContext->requestHeaders = $context->requestHeaders; 37 | $result = $this->client->invoke($fullname, $args, $clientContext); 38 | $context->responseHeaders = $clientContext->responseHeaders; 39 | return $result; 40 | } 41 | public function use (callable ...$handlers): self { 42 | $this->client->use(...$handlers); 43 | return $this; 44 | } 45 | public function unuse(callable ...$handlers): self { 46 | $this->client->unuse(...$handlers); 47 | return $this; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/LoadBalance/NginxRoundRobinLoadBalance.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins\LoadBalance; 16 | 17 | use Hprose\RPC\Core\Context; 18 | use Throwable; 19 | 20 | class NginxRoundRobinLoadBalance extends WeightedLoadBalance { 21 | private $effectiveWeights; 22 | private $currentWeights; 23 | public function __construct(array $uriList) { 24 | parent::__construct($uriList); 25 | $this->effectiveWeights = $this->weights; 26 | $this->currentWeights = array_fill(0, count($this->uris), 0); 27 | } 28 | public function handler(string $request, Context $context, callable $next): string { 29 | $n = count($this->uris); 30 | $index = -1; 31 | $totalWeight = array_sum($this->effectiveWeights); 32 | if ($totalWeight > 0) { 33 | $currentWeight = log(0); 34 | for ($i = 0; $i < $n; ++$i) { 35 | $weight = ($this->currentWeights[$i] += $this->effectiveWeights[$i]); 36 | if ($currentWeight < $weight) { 37 | $currentWeight = $weight; 38 | $index = $i; 39 | } 40 | } 41 | $this->currentWeights[$index] = $currentWeight - $totalWeight; 42 | } else { 43 | $index = random_int(0, $n - 1); 44 | } 45 | $context->uri = $this->uris[$index]; 46 | try { 47 | $response = $next($request, $context); 48 | if ($this->effectiveWeights[$index] < $this->weights[$index]) { 49 | ++$this->effectiveWeights[$index]; 50 | } 51 | return $response; 52 | } catch (Throwable $e) { 53 | if ($this->effectiveWeights[$index] > 0) { 54 | --$this->effectiveWeights[$index]; 55 | } 56 | throw $e; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/LoadBalance/RandomLoadBalance.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins\LoadBalance; 16 | 17 | use Hprose\RPC\Core\Context; 18 | 19 | class RandomLoadBalance { 20 | public function handler(string $request, Context $context, callable $next): string { 21 | $uris = $context->client->getUris(); 22 | $n = count($uris); 23 | $context->uri = $uris[random_int(0, $n - 1)]; 24 | return $next($request, $context); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/LoadBalance/RoundRobinLoadBalance.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins\LoadBalance; 16 | 17 | use Hprose\RPC\Core\Context; 18 | 19 | class RoundRobinLoadBalance { 20 | private $index = -1; 21 | public function handler(string $request, Context $context, callable $next): string { 22 | $uris = $context->client->getUris(); 23 | $n = count($uris); 24 | if ($n > 1) { 25 | $this->index = ($this->index + 1) % $n; 26 | $context->uri = $uris[$this->index]; 27 | } 28 | return $next($request, $context); 29 | } 30 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/LoadBalance/WeightedLeastActiveLoadBalance.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins\LoadBalance; 16 | 17 | use Hprose\RPC\Core\Context; 18 | use Throwable; 19 | 20 | class WeightedLeastActiveLoadBalance extends WeightedLoadBalance { 21 | private $effectiveWeights; 22 | private $actives; 23 | public function __construct(array $uriList) { 24 | parent::__construct($uriList); 25 | $this->effectiveWeights = $this->weights; 26 | $this->actives = array_fill(0, count($this->uris), 0); 27 | } 28 | public function handler(string $request, Context $context, callable $next): string { 29 | $leastActive = min($this->actives); 30 | $leastActiveIndexes = []; 31 | $totalWeight = 0; 32 | $n = count($this->weights); 33 | for ($i = 0; $i < $n; ++$i) { 34 | if ($this->actives[$i] === $leastActive) { 35 | $leastActiveIndexes[] = $i; 36 | $totalWeight += $this->effectiveWeights[$i]; 37 | } 38 | } 39 | $index = $leastActiveIndexes[0]; 40 | $n = count($leastActiveIndexes); 41 | if ($n > 1) { 42 | if ($totalWeight > 0) { 43 | $currentWeight = random_int(0, $totalWeight - 1); 44 | for ($i = 0; $i < $n; ++$i) { 45 | $currentWeight -= $this->effectiveWeights[$leastActiveIndexes[$i]]; 46 | if ($currentWeight < 0) { 47 | $index = $leastActiveIndexes[$i]; 48 | break; 49 | } 50 | } 51 | } else { 52 | $index = $leastActiveIndexes[random_int(0, $n - 1)]; 53 | } 54 | } 55 | $context->uri = $this->uris[$index]; 56 | ++$this->actives[$index]; 57 | try { 58 | $response = $next($request, $context); 59 | --$this->actives[$index]; 60 | if ($this->effectiveWeights[$index] < $this->weights[$index]) { 61 | ++$this->effectiveWeights[$index]; 62 | } 63 | return $response; 64 | } catch (Throwable $e) { 65 | --$this->actives[$index]; 66 | if ($this->effectiveWeights[$index] > 0) { 67 | --$this->effectiveWeights[$index]; 68 | } 69 | throw $e; 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/LoadBalance/WeightedLoadBalance.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins\LoadBalance; 16 | 17 | use Hprose\RPC\Core\Context; 18 | use InvalidArgumentException; 19 | 20 | abstract class WeightedLoadBalance { 21 | protected $uris = []; 22 | protected $weights = []; 23 | public function __construct(array $uriList) { 24 | if (empty($uriList)) { 25 | throw new InvalidArgumentException('uriList cannot be empty'); 26 | } 27 | foreach ($uriList as $uri => $weight) { 28 | $this->uris[] = $uri; 29 | if ($weight <= 0) { 30 | throw new InvalidArgumentException('weight must be great than 0'); 31 | } 32 | $this->weights[] = $weight; 33 | } 34 | } 35 | public abstract function handler(string $request, Context $context, callable $next): string; 36 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/LoadBalance/WeightedRandomLoadBalance.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins\LoadBalance; 16 | 17 | use Hprose\RPC\Core\Context; 18 | use Throwable; 19 | 20 | class WeightedRandomLoadBalance extends WeightedLoadBalance { 21 | private $effectiveWeights; 22 | public function __construct(array $uriList) { 23 | parent::__construct($uriList); 24 | $this->effectiveWeights = $this->weights; 25 | } 26 | public function handler(string $request, Context $context, callable $next): string { 27 | $n = count($this->uris); 28 | $index = $n - 1; 29 | $totalWeight = array_sum($this->effectiveWeights); 30 | if ($totalWeight > 0) { 31 | $currentWeight = random_int(0, $totalWeight - 1); 32 | for ($i = 0; $i < $n; ++$i) { 33 | $currentWeight -= $this->effectiveWeights[$i]; 34 | if ($currentWeight < 0) { 35 | $index = $i; 36 | break; 37 | } 38 | } 39 | } else { 40 | $index = random_int(0, $n - 1); 41 | } 42 | $context->uri = $this->uris[$index]; 43 | try { 44 | $response = $next($request, $context); 45 | if ($this->effectiveWeights[$index] < $this->weights[$index]) { 46 | ++$this->effectiveWeights[$index]; 47 | } 48 | return $response; 49 | } catch (Throwable $e) { 50 | if ($this->effectiveWeights[$index] > 0) { 51 | --$this->effectiveWeights[$index]; 52 | } 53 | throw $e; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/LoadBalance/WeightedRoundRobinLoadBalance.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins\LoadBalance; 16 | 17 | use Hprose\RPC\Core\Context; 18 | 19 | class WeightedRoundRobinLoadBalance extends WeightedLoadBalance { 20 | private $maxWeight; 21 | private $gcdWeight; 22 | private $index = -1; 23 | private $currentWeight = 0; 24 | public function __construct(array $uriList) { 25 | parent::__construct($uriList); 26 | $this->maxWeight = max($this->weights); 27 | $this->gcdWeight = array_reduce($this->weights, function ($x, $y) { 28 | $x = (int) $x; 29 | $y = (int) $y; 30 | if ($x < $y) { 31 | [$x, $y] = [$y, $x]; 32 | } 33 | while ($y !== 0) { 34 | [$x, $y] = [$y, $x % $y]; 35 | } 36 | return $x; 37 | }); 38 | } 39 | public function handler(string $request, Context $context, callable $next): string { 40 | $n = count($this->uris); 41 | while (true) { 42 | $this->index = ($this->index + 1) % $n; 43 | if ($this->index === 0) { 44 | $this->currentWeight -= $this->gcdWeight; 45 | if ($this->currentWeight <= 0) { 46 | $this->currentWeight = $this->maxWeight; 47 | } 48 | } 49 | if ($this->weights[$this->index] >= $this->currentWeight) { 50 | $context->uri = $this->uris[$this->index]; 51 | return $next($request, $context); 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Plugins/Log.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC\Plugins; 16 | 17 | use Hprose\RPC\Core\Context; 18 | use Throwable; 19 | 20 | class Log { 21 | public $enabled; 22 | public $message_type = 0; 23 | public $destination = null; 24 | public $extra_headers = null; 25 | public function __construct(bool $enabled = true) { 26 | $this->enabled = $enabled; 27 | } 28 | public function ioHandler(string $request, Context $context, callable $next): string { 29 | $enabled = $context['log'] ?? $this->enabled; 30 | if (!$enabled) { 31 | return $next($request, $context); 32 | } 33 | error_log(str_replace("\0", '\\0', $request), $this->message_type, $this->destination, $this->extra_headers); 34 | $response = $next($request, $context); 35 | error_log(str_replace("\0", '\\0', $response), $this->message_type, $this->destination, $this->extra_headers); 36 | return $response; 37 | } 38 | public function invokeHandler(string $name, array &$args, Context $context, callable $next) { 39 | $enabled = $context['log'] ?? $this->enabled; 40 | if (!$enabled) { 41 | return $next($name, $args, $context); 42 | } 43 | try { 44 | $a = $args; 45 | $result = $next($name, $args, $context); 46 | error_log("$name(" . substr(json_encode($a), 1, -1) . ') = ' . json_encode($result), $this->message_type, $this->destination, $this->extra_headers); 47 | } catch (Throwable $e) { 48 | error_log($e->getMessage() . "\n" . $e->getTraceAsString(), $this->message_type, $this->destination, $this->extra_headers); 49 | throw $e; 50 | } 51 | return $result; 52 | } 53 | } -------------------------------------------------------------------------------- /src/Hprose/RPC/Service.php: -------------------------------------------------------------------------------- 1 | | 12 | | | 13 | \*________________________________________________________*/ 14 | 15 | namespace Hprose\RPC; 16 | 17 | class_alias('Hprose\\RPC\\Core\\Service', 'Hprose\\RPC\\Service'); 18 | 19 | if (!Service::isRegister('mock')) { 20 | Service::register('mock', 'Hprose\\RPC\\Mock\\MockHandler'); 21 | } 22 | if (!Service::isRegister('http')) { 23 | Service::register('http', 'Hprose\\RPC\\Http\\HttpHandler'); 24 | } 25 | --------------------------------------------------------------------------------