bit3 / remote-objects
远程对象方法调用框架
Requires (Dev)
- monolog/monolog: ~1.3
- phpseclib/phpseclib: *
- phpunit/phpunit: ~3
Suggests
- monolog/monolog: Required for logging.
- phpseclib/phpseclib: Required for encrypted transport.
This package is not auto-updated.
Last update: 2022-02-01 12:23:25 UTC
README
又是一个方法调用框架?难道XML-RPC、JSON-RPC和类似实现还不够多吗?是的,有很多,但它们的灵活性不足,而且大多数没有提供加密等安全特性。
RemoteObjects的优势在于提供一个安全(允许加密)、灵活(允许不同的传输方式,如Unix套接字)且互操作(默认支持JSON-RPC 2.0,但也支持XML-RPC或其他协议)的远程方法调用框架。每个组件都是可替换的,如果你想的话,你还可以通过SMS作为传输层调用方法,或者用你自己的协议而不是JSON-RPC。一切皆有可能!
换句话说:这个框架结合了所有其他框架的优点 ;-)
工作原理
user -> Client::invoke($method, $params)
\
\--> Encoder::encodeMethod($method, $params)
/
$request <-/
\
\--> Transport\Client::request($request)
|
|
HTTP or something else :-)
|
V
Transport\Server::receive()
/
$request <-/
\
\--> Encoder::decodeMethod($request)
/
[$method, $params] <-/
\
\--> Server::invokeArgs($method, $params)
\
\--> $target->$method($params..)
/
$result <--/
\
\--> Encoder::encodeResult($result)
/
$response <--/
\
\--> Transport\Server:respond($response)
|
|
All the way back
|
V
Transport\Client::request(...)
/
$response <--/
\
\--> Encoder::decodeResult($response)
/
$result <--/
/
user <--/
使用示例
使用JSON-RPC 2.0作为协议,Unix套接字作为传输层的使用示例。
server.php
class RemoteObject { public function reply($name) { return 'Hello ' . $name; } } $transport = new RemoteObjects\Transport\UnixSocketServer('/tmp/socket.server'); $encoder = new RemoteObjects\Encode\JsonRpc20Encoder(); $target = new RemoteObject(); $server = new RemoteObjects\Server( $transport, $encoder, $target ); $server->handle();
client.php
$transport = new RemoteObjects\Transport\UnixSocketClient('/tmp/socket.client', '/tmp/socket.server'); $encoder = new RemoteObjects\Encode\JsonRpc20Encoder(); $server = new RemoteObjects\Client( $transport, $encoder ); $result = $server->invoke('reply', 'Tristan'); echo $result; // -> "Hello Tristan"
链式和深层结构
受到另一个JSON-RPC库的启发,RemoteObjects允许服务器端有复杂的结构。有时你需要一个复杂的远程API,但又不想使你的远程对象类过于复杂。而不是创建多个端点,可以创建多个“命名”远程对象。
创建命名对象非常简单
$server = new RemoteObjects\Server( $transport, $encoder, array( 'a' => $targetA, 'b' => $targetB ) );
要从$targetA
或$targetB
调用方法,只需在方法名前加上对象名,后跟一个点。
$client->invoke('a.methodA'); // invoke $targetA->methodA(); $client->invoke('b.methodB'); // invoke $targetB->methodB();
也可以创建更大、更复杂的多维结构。
$server = new RemoteObjects\Server( $transport, $encoder, array( 'a' => array( 'one' => $targetOne, 'two' => array( 'I' => $targetI, 'II' => $targetII ) ), 'b' => $targetB ) );
例如,要调用$targetII->method()
,方法名将是a.two.II.method
。
提示:您也可以使用兼容ArrayAccess
的对象,而不是数组!
为了更好地进行程序性链式调用,可以通过RemoteObject
对象的虚拟属性访问结构。访问RemoteObject
的属性将给出一个新的RemoteObject
实例,指向该命名路径(类似于Client::getRemoteObject(<name>)
)。
根据前面的示例,也可以这样访问a.two.II.method
$result = $client ->castAsRemoteObject() ->a ->two ->II ->method();
懒加载对象
在最后一章中,您了解了链式和深层结构。但如果您的API增长,创建所有目标对象可能会浪费很多资源。而不是使用数组或ArrayAccess
对象,您可以为命名对象创建一个有getter的方法类。
class Objects { public function getA() { return new TargetA(); } public function getB() { return new TargetB(); } }
然后使用这个对象作为目标。
$server = new RemoteObjects\Server( $transport, $encoder, new Objects() );
要访问TargetA
或TargetB
的方法,与之前相同,使用a.methodName
来调用TargetA::methodName
,或使用b.methodName
来调用TargetB::methodName
。
远程对象访问器
RemoteObjects允许创建RemoteObject
对象来直接调用其上的方法。
在服务器端
$server = new RemoteObjects\Server( $transport, $encoder, array( 'a' => $targetA, 'b' => $targetB ) );
在客户端
$remoteA = $client->getRemoteObject('a'); $remoteB = $client->getRemoteObject('b');
现在 $remoteA
是对 $targetA
的访问器,而 $remoteB
是对 $targetB
的访问器。请注意,$remoteA
和 $remoteB
只是无任何方法的代理。每个方法都是动态传递到服务器端的。
如果服务器端没有命名对象,您也可以将整个客户端对象打包为 RemoteObject
。
$remote = $client->castAsRemoteObject();
现在每次调用 $remote->method($arg1, $arg2, ...)
将直接传递到 $client->invokeArgs('method', [$arg1, $arg2, ...])
。
类型映射
远程方法调用的一大问题,以及之前展示的方法是远程方法的不可知性。您使用 Client::getRemoteObject
或 Client::castAsRemoteObject
获取的每个对象只是一个原始代理,没有任何方法。您无法使用 method_exists
或 ReflectionClass
来“检查”对象及其方法。
为了解决这个问题,RemoteObjects 允许您指定一个类型,用作 RemoteObject
。只需在调用 Client::getRemoteObject
或 Client::castAsRemoteObject
时指定您的类型。
interface MyRemote { public function remoteMethod(); } $remote = $client->castAsRemoteObject('MyRemote');
从版本 1.2 开始,您也可以使用一个类。
class MyRemote { public function remoteMethod() { // ... } } $remote = $client->castAsRemoteObject('MyRemote');
对象 $remote
是 MyRemote
的实例,但它也是 RemoteObjects\RemoteObject
的实例。
这是如何工作的?内部生成一个虚拟临时代理类,命名为 RemoteProxies\__WS__\MyRemote
(命名空间前缀,在早期版本中类名后缀),它实现了该接口。
使用这种技术,您可以几乎将任何对象远程化,并将其传递给其他方法而不发生类型提示不匹配。您所需的一切只是一个接口。
已知限制
- 远程对象无法通过引用传递参数。
- 无法序列化的参数无法传输到远程端点。
安全性
有时您希望提高安全性,但您的传输层不支持加密(例如,由于某些原因无法使用 HTTPS)。RemoteObjects 提供了 AesEncoder
和 RsaEncoder
,可以在传输前加密数据,在评估前解密。
使用 AesEncoder
或 RsaEncoder
非常简单。
AES
服务器和客户端
$jsonEncoder = new RemoteObjects\Encode\JsonRpc20Encoder(); $encoder = new RemoteObjects\Encode\AesEncoder( $jsonEncoder, '<add your pre-shared key here>' );
RSA
服务器
$jsonEncoder = new RemoteObjects\Encode\JsonRpc20Encoder(); $encoder = new RemoteObjects\Encode\RsaEncoder( $jsonEncoder, '<add client public key here>', '<add server private key here>' );
客户端
$jsonEncoder = new RemoteObjects\Encode\JsonRpc20Encoder(); $encoder = new RemoteObjects\Encode\RsaEncoder( $jsonEncoder, '<add server public key here>', '<add client private key here>' );
日志记录
Monolog 受支持,但不是必需的。您可以在每个传输、编码器或服务器/客户端对象中添加一个记录器来记录错误和调试信息。
$logger = new \Monolog\Logger('RemoteObjects'); $logger->pushHandler(new StreamHandler('php://stderr', Logger::ERROR)); $transport->setLogger($logger); $encoder->setLogger($logger); $server->setLogger($logger); // or $client->setLogger($logger);