Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions reference/yar/book.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: 75d48299ef85693f10eee31af1be88488fb84c6f Maintainer: mowangjuanzi Status: ready -->
<!-- EN-Revision: 60ce1c5c7cc6d340191828de21f1d6b48cd83807 Maintainer: mowangjuanzi Status: ready -->
<book xml:id="book.yar" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<?phpdoc extension-membership="pecl" ?>
<title>Yet Another RPC Framework</title>
Expand All @@ -15,14 +15,16 @@
<simpara>
Yar 是一个原生 PHP 扩展,而不是用户态的库。它基于 HTTP、HTTPS 或 TCP
使用紧凑的二进制协议,并内置三种打包器(<literal>php</literal>、<literal>json</literal>,以及在使用
<option role="configure">--enable-msgpack</option> 编译时的
<literal>msgpack</literal>),因此不需要额外的依赖包或代理进程
<option role="configure">--enable-msgpack</option> 编译时可用的
<literal>msgpack</literal>),因此无需安装额外的依赖包或代理进程
</simpara>
<simpara>
该协议与语言无关:目前已存在 C、Java 和 Lua 的兼容实现。
该协议与语言无关,任何语言都可以轻松实现与 Yar 服务的通信,
具体细节参见 <link linkend="yar.protocol">Yar 协议</link>。
</simpara>
</preface>

&reference.yar.protocol;
&reference.yar.setup;
&reference.yar.constants;
&reference.yar.examples;
Expand Down
15 changes: 14 additions & 1 deletion reference/yar/constants.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: 75d48299ef85693f10eee31af1be88488fb84c6f Maintainer: mowangjuanzi Status: ready -->
<!-- EN-Revision: 60ce1c5c7cc6d340191828de21f1d6b48cd83807 Maintainer: mowangjuanzi Status: ready -->
<appendix xml:id="yar.constants" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
&reftitle.constants;
&extension.constants;
Expand Down Expand Up @@ -302,6 +302,19 @@
</simpara>
</listitem>
</varlistentry>
<varlistentry xml:id="constant.yar-err-forbidden">
<term>
<constant>YAR_ERR_FORBIDDEN</constant>
(<type>int</type>)
</term>
<listitem>
<simpara>
请求被服务器端的身份验证拒绝(参见
<link linkend="yar.protocol">Yar 协议头</link>的
<literal>provider</literal> 和 <literal>token</literal> 字段)。
</simpara>
</listitem>
</varlistentry>
</variablelist>
</appendix>

Expand Down
72 changes: 60 additions & 12 deletions reference/yar/examples.xml
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: 75d48299ef85693f10eee31af1be88488fb84c6f Maintainer: mowangjuanzi Status: ready -->
<!-- EN-Revision: 60ce1c5c7cc6d340191828de21f1d6b48cd83807 Maintainer: mowangjuanzi Status: ready -->
<!-- CREDITS: Luffy -->
<chapter xml:id="yar.examples" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
&reftitle.examples;
<simpara>
下面的示例演示了一个完整的服务:一个暴露若干算术方法的服务端、
一个调用它们的同步客户端、一个一次性发出多个并发调用的客户端,
以及一个通过 TCP 与服务器通信的客户端。
</simpara>

<example>
<title>Yar 服务端示例</title>
<simpara>
Yar 服务就是一个由 <classname>Yar_Server</classname>
包装的普通 PHP 类。对象的每一个公开方法都会成为一个 RPC
端点,而受保护方法、私有方法以及方法名以下划线开头的方法,
对客户端不可见。公开方法的文档注释会被收集起来,
显示在服务信息页面上。
</simpara>
<simpara>
RPC 请求以 HTTP POST 请求的形式到达,请求体携带 Yar 二进制协议
负载,因此这个脚本通常被映射为常规 Web 服务器上的一个 URI。
</simpara>
<programlisting role="php">
<![CDATA[
<?php
/* 假设这个页面可以通过 http://example.com/operator.php 访问 */
/* 假设这个页面可以通过 http://api.example.com/operator.php 访问 */
class Operator {
Expand Down Expand Up @@ -57,10 +74,12 @@ $server->handle();
<example>
<title>通过浏览器访问服务端(GET 请求)</title>
<simpara>
当向服务地址发起 GET 请求时,Yar 会渲染一个信息页面,
当向服务地址发起 GET 请求时(例如直接在浏览器中打开它),
Yar 不会执行 RPC 调用,而是渲染一个信息页面,
列出执行对象的每一个公开方法及其文档注释。
该行为由
<link linkend="ini.yar.expose-info">yar.expose_info</link> 配置项控制。
<link linkend="ini.yar.expose-info">yar.expose_info</link>
配置项控制;当它关闭时,GET 请求将失败。
</simpara>
&example.outputs.similar;
<mediaobject>
Expand All @@ -73,18 +92,30 @@ $server->handle();

<example>
<title>Yar 客户端示例</title>
<simpara>
<classname>Yar_Client</classname> 绑定到单一的服务地址。
在它上面调用任何未定义的方法,都会被透明地转换为同步
RPC 调用,远程方法用起来和本地方法一样;
<methodname>Yar_Client::call</methodname>
显式地按方法名做同样的事情。
</simpara>
<simpara>
受保护方法不会被暴露:调用它们会失败,并抛出错误码为
<constant>YAR_ERR_REQUEST</constant> 的
<exceptionname>Yar_Client_Exception</exceptionname>。
</simpara>
<programlisting role="php">
<![CDATA[
<?php
$client = new Yar_Client("http://example.com/operator.php");
$client = new Yar_Client("http://api.example.com/operator.php");
/* 直接调用 */
var_dump($client->add(1, 2));
/* 通过 call() 调用 */
var_dump($client->call("add", array(3, 2)));
/* _add 无法被调用: 它不是公开方法 */
/* _add 无法被调用它不是公开方法 */
var_dump($client->_add(1, 2));
?>
]]>
Expand All @@ -94,17 +125,33 @@ var_dump($client->_add(1, 2));
<![CDATA[
int(3)
int(5)
PHP Fatal error: Uncaught Yar_Server_Request_Exception: call to undefined api Operator::_add() in *
PHP Fatal error: Uncaught Yar_Client_Exception: call to undefined api Operator::_add() in *
]]>
</screen>
</example>

<example>
<title>Yar 并发客户端示例</title>
<simpara>
<classname>Yar_Concurrent_Client</classname>
不是逐个调用服务,而是先注册多个调用,然后通过
<methodname>Yar_Concurrent_Client::loop</methodname>
一次性发出所有调用。响应按到达的顺序传递给回调函数,
而不是按调用注册的顺序。
</simpara>
<simpara>
在全部请求发送完毕后,回调函数会以 &null;
参数被调用一次,以便调用方知道没有更多请求等待发送;
下面的示例检查了这个通知。
</simpara>
<programlisting role="php">
<![CDATA[
<?php
function callback($ret, $callinfo) {
if ($callinfo == NULL) {
/* all requests are sent, waiting for the responses */
return;
}
echo $callinfo['method'], " result: ", $ret, "\n";
}
Expand All @@ -113,9 +160,9 @@ function error_callback($type, $error, $callinfo) {
}
/* 注册对远程服务的异步调用 */
Yar_Concurrent_Client::call("http://example.com/operator.php", "add", array(1, 2), "callback");
Yar_Concurrent_Client::call("http://example.com/operator.php", "sub", array(2, 1), "callback");
Yar_Concurrent_Client::call("http://example.com/operator.php", "mul", array(2, 2), "callback");
Yar_Concurrent_Client::call("http://api.example.com/operator.php", "add", array(1, 2), "callback");
Yar_Concurrent_Client::call("http://api.example.com/operator.php", "sub", array(2, 1), "callback");
Yar_Concurrent_Client::call("http://api.example.com/operator.php", "mul", array(2, 2), "callback");
/* 发送所有请求并等待响应 */
Yar_Concurrent_Client::loop("callback", "error_callback");
Expand All @@ -137,8 +184,9 @@ add result: 3
<simpara>
除了 HTTP 之外,<classname>Yar_Client</classname> 还可以
通过 TCP 或 Unix socket 与兼容 Yar 协议的服务器通信,
例如一个由 Yar C 框架实现的服务。
远程服务器必须实现相同的 Yar 二进制协议。
例如一个由
<link xlink:href="&url.git.hub;laruence/yar-c">Yar C 框架</link>
实现的服务,它提供的二进制 Yar 协议与 PHP 服务端使用的相同。
</simpara>
<programlisting role="php">
<![CDATA[
Expand Down
131 changes: 131 additions & 0 deletions reference/yar/protocol.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: 60ce1c5c7cc6d340191828de21f1d6b48cd83807 Maintainer: Laruence Status: ready -->
<chapter xml:id="yar.protocol" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Yar 协议</title>
<simpara>
Yar 不依赖 schema 或 IDL 文件:网络上传输的一切都是纯字节。
任何能够读写字节的语言都可以与 Yar 服务通信,完全不需要安装任何框架——
只需构造一个固定大小的二进制请求头和一个序列化后的请求体,
把它们发送到服务 URI,再解析响应即可。
</simpara>
<simpara>
一条消息由一个固定大小为 82 字节的头部和紧随其后的消息体组成。
头部的布局与下面的 C 结构体完全一致,紧凑排列、没有填充字节,
并按声明顺序逐个字段写入网络:
</simpara>
<programlisting role="c">
<![CDATA[
typedef struct _yar_header {
uint32_t id; /* transaction id */
uint16_t version; /* protocol version, currently always 0 */
uint32_t magic_num; /* must be 0x80DFEC60 */
uint32_t reserved;
unsigned char provider[32]; /* request from whom (authentication) */
unsigned char token[32]; /* request token (authentication) */
uint32_t body_len; /* length of the whole body, including
the packager identifier */
} __attribute__ ((packed)) yar_header_t;
]]>
</programlisting>
<simpara>
其中 <literal>id</literal>、<literal>magic_num</literal>、<literal>reserved</literal>
和 <literal>body_len</literal> 字段以网络字节序(大端)存储;
其余字段是原始字节。
</simpara>
<simpara>
消息体以一个 8 字节的打包器标识符开头——<literal>PHP</literal>、<literal>JSON</literal>
或 <literal>MSGPACK</literal>,不足部分以零填充——用于告知接收方其余内容的编码方式,
其后是序列化内容本身。
</simpara>
<itemizedlist>
<listitem>
<simpara>
请求体解码后是一个数组,包含以下键:<literal>i</literal>(事务 id)、<literal>m</literal>
(被调用的方法)和 <literal>p</literal>(参数列表)。
</simpara>
</listitem>
<listitem>
<simpara>
响应体解码后是一个数组,包含以下键:<literal>i</literal>(事务 id)、<literal>s</literal>
(状态,取值为 <literal>YAR_ERR_*</literal> 常量之一)、<literal>r</literal>(返回值)、
<literal>o</literal>(服务方法产生的任何输出)以及 <literal>e</literal>
(调用失败时的错误或异常)。
</simpara>
</listitem>
</itemizedlist>
<simpara>
通过 HTTP 传输时,消息作为 POST 请求的正文发送,响应作为回复的正文到达;
通过 TCP 或 Unix socket 传输时,消息直接写入流中。
</simpara>
<example>
<title>在不安装扩展的情况下调用 Yar 服务</title>
<simpara>
下面这个独立脚本仅使用标准 socket,就为 <literal>php</literal>
打包器构造了一个有效的 Yar 请求,将其发送到服务 URI,
并输出解码后的响应。用
<link linkend="yar.examples">示例</link>中的
<classname>Operator</classname> 服务运行该脚本,输出为
<literal>int(3)</literal>。
</simpara>
<programlisting role="php">
<![CDATA[
<?php

$uri = "http://api.example.com/operator.php";

/* 1. the body: packager identifier + serialized request */
$serialized = serialize(array("i" => 1, "m" => "add", "p" => array(1, 2)));
$body = str_pad("PHP", 8, "\0") . $serialized;

/* 2. the header: 82 bytes, multi-byte integers in network byte order */
$header = pack("N", 1) /* id */
. pack("v", 0) /* version */
. pack("N", 0x80DFEC60) /* magic number */
. pack("N", 0) /* reserved */
. str_pad("", 32, "\0") /* provider */
. str_pad("", 32, "\0") /* token */
. pack("N", strlen($body)); /* body length */

/* 3. send it as the body of a POST request */
$stream = stream_context_create(array("http" => array(
"method" => "POST",
"header" => "Content-Type: application/octet-stream\r\n",
"content" => $header . $body,
)));
$reply = file_get_contents($uri, false, $stream);

/* 4. parse the reply: 82-byte header, then the response body */
$response = unserialize(substr($reply, 82 + 8));
var_dump($response["r"]);
?>
]]>
</programlisting>
</example>
<simpara>
一个更完整的纯 PHP 客户端实现位于
<link xlink:href="&url.git.hub;laruence/yar">Yar 源码仓库</link>的
<literal>tools/</literal> 目录中,它还会解码响应头,并支持并发调用。
</simpara>
</chapter>

<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->
Loading