bit3/remote-objects

此包已被废弃,不再维护。未建议替代包。

远程对象方法调用框架

1.2.1 2013-07-31 22:14 UTC

This package is not auto-updated.

Last update: 2022-02-01 12:23:25 UTC


README

Build Status API DOCS

又是一个方法调用框架?难道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()
);

要访问TargetATargetB的方法,与之前相同,使用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::getRemoteObjectClient::castAsRemoteObject 获取的每个对象只是一个原始代理,没有任何方法。您无法使用 method_existsReflectionClass 来“检查”对象及其方法。

为了解决这个问题,RemoteObjects 允许您指定一个类型,用作 RemoteObject。只需在调用 Client::getRemoteObjectClient::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 提供了 AesEncoderRsaEncoder,可以在传输前加密数据,在评估前解密。

使用 AesEncoderRsaEncoder 非常简单。

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);