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
6 changes: 4 additions & 2 deletions reference/yar/book.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
are required.
</simpara>
<simpara>
The protocol is language agnostic: compatible implementations exist
for C, Java and Lua.
The protocol is language agnostic, so services can be consumed by any
language; see <link linkend="yar.protocol">The Yar Protocol</link> for
the details.
</simpara>
</preface>

&reference.yar.protocol;
&reference.yar.setup;
&reference.yar.constants;
&reference.yar.examples;
Expand Down
14 changes: 14 additions & 0 deletions reference/yar/constants.xml
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,20 @@
</simpara>
</listitem>
</varlistentry>
<varlistentry xml:id="constant.yar-err-forbidden">
<term>
<constant>YAR_ERR_FORBIDDEN</constant>
(<type>int</type>)
</term>
<listitem>
<simpara>
The request was rejected by the server's authentication
(see the <literal>provider</literal> and
<literal>token</literal> fields of the
<link linkend="yar.protocol">Yar protocol header</link>).
</simpara>
</listitem>
</varlistentry>
</variablelist>
</appendix>

Expand Down
77 changes: 64 additions & 13 deletions reference/yar/examples.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,33 @@

<chapter xml:id="yar.examples" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
&reftitle.examples;
<simpara>
The examples below walk through a complete service: a server that
exposes a few arithmetic methods, a synchronous client that calls
them, a client that fans several calls out concurrently, and a
client that talks to a server over TCP.
</simpara>

<example>
<title>Yar Server Example</title>
<simpara>
A Yar service is just a regular PHP class wrapped by
<classname>Yar_Server</classname>. Every public method of the
object becomes an RPC endpoint; protected and private methods, as
well as methods whose names start with an underscore, stay hidden
from clients. The doc comments of the public methods are collected
and shown on the service information page.
</simpara>
<simpara>
RPC requests arrive as HTTP POST requests carrying a binary Yar
protocol payload, so the script is usually mapped to a URI on a
regular web server.
</simpara>
<programlisting role="php">
<![CDATA[
<?php

/* assume this page can be accessed by http://example.com/operator.php */
/* assume this page can be accessed by http://api.example.com/operator.php */

class Operator {

Expand Down Expand Up @@ -56,11 +76,12 @@ $server->handle();
<example>
<title>Access the server in browser (GET request)</title>
<simpara>
When a GET request is issued to the service URI, Yar renders an
information page listing every public method of the executor object
together with its doc comment. This is controlled by the
<link linkend="ini.yar.expose-info">yar.expose_info</link>
directive.
When a GET request is issued to the service URI — for instance by
opening it in a browser — Yar does not perform an RPC call but
renders an information page listing every public method of the
executor object together with its doc comment. This is controlled
by the <link linkend="ini.yar.expose-info">yar.expose_info</link>
directive; when it is off, a GET request fails instead.
</simpara>
&example.outputs.similar;
<mediaobject>
Expand All @@ -73,10 +94,22 @@ $server->handle();

<example>
<title>Yar Client Example</title>
<simpara>
A <classname>Yar_Client</classname> is bound to a single service
address. Calling any undefined method on it is transparently turned
into a synchronous RPC call, so remote methods look and feel like
local ones; <methodname>Yar_Client::call</methodname> does the same
thing explicitly by name.
</simpara>
<simpara>
Protected methods are not exposed: calling one fails with a
<exceptionname>Yar_Client_Exception</exceptionname> whose code is
<constant>YAR_ERR_REQUEST</constant>.
</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");

/* call directly */
var_dump($client->add(1, 2));
Expand All @@ -94,17 +127,34 @@ 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 Concurrent Client Example</title>
<simpara>
Instead of calling services one after another,
<classname>Yar_Concurrent_Client</classname> registers several calls
first and then dispatches them all at once with
<methodname>Yar_Concurrent_Client::loop</methodname>. The responses
are passed to the callback in the order they arrive, not in the
order the calls were registered.
</simpara>
<simpara>
Right after all requests have been sent, the callback is invoked
once with &null; arguments so that the caller knows no further
request is pending; the example below checks for this notification.
</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 +163,9 @@ function error_callback($type, $error, $callinfo) {
}

/* register async calls to remote services */
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");

/* send all requests and wait for the responses */
Yar_Concurrent_Client::loop("callback", "error_callback");
Expand All @@ -137,8 +187,9 @@ add result: 3
<simpara>
Besides HTTP, <classname>Yar_Client</classname> can talk to Yar
compatible servers over TCP or Unix sockets, for example a service
implemented with the Yar C framework. The remote server must
implement the same binary Yar protocol.
implemented with the
<link xlink:href="&url.git.hub;laruence/yar-c">Yar C framework</link>,
which serves the same binary Yar protocol that the PHP server uses.
</simpara>
<programlisting role="php">
<![CDATA[
Expand Down
143 changes: 143 additions & 0 deletions reference/yar/protocol.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->

<chapter xml:id="yar.protocol" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>The Yar Protocol</title>
<simpara>
Yar does not rely on a schema or IDL file: everything is exchanged on
the wire as plain bytes. Any language that can read and write bytes
can speak to a Yar service, without installing any framework at all —
build one fixed-size binary header and a serialized request body,
send them to the service URI, and parse the reply.
</simpara>
<simpara>
A message consists of a fixed-size header of 82 bytes followed by a
body. The header is laid out exactly like the following C structure,
packed with no padding, and is written to the wire field after field
in declaration order:
</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>
The <literal>id</literal>, <literal>magic_num</literal>,
<literal>reserved</literal> and <literal>body_len</literal> fields
are stored in network byte order (big-endian); the remaining fields
are raw bytes.
</simpara>
<simpara>
The body starts with an 8-byte packager identifier —
<literal>PHP</literal>, <literal>JSON</literal> or
<literal>MSGPACK</literal>, zero-padded — telling the receiver how
the remainder was encoded, followed by the serialized content itself.
</simpara>
<itemizedlist>
<listitem>
<simpara>
The request body decodes to an array with the keys
<literal>i</literal> (the transaction id), <literal>m</literal>
(the method being called) and <literal>p</literal> (the list of
parameters).
</simpara>
</listitem>
<listitem>
<simpara>
The response body decodes to an array with the keys
<literal>i</literal> (the transaction id), <literal>s</literal>
(the status, one of the <literal>YAR_ERR_*</literal> codes),
<literal>r</literal> (the return value), <literal>o</literal> (any
output the service method produced) and <literal>e</literal> (the
error or exception, when the call failed).
</simpara>
</listitem>
</itemizedlist>
<simpara>
Over HTTP the message is sent as the body of a POST request, with
the response arriving as the body of the reply; over TCP or Unix
sockets it is written directly on the stream.
</simpara>
<example>
<title>Calling a Yar service without the extension</title>
<simpara>
The following self-contained script builds a valid Yar request for
the <literal>php</literal> packager with nothing but standard
sockets, sends it to a service URI, and prints the decoded
response. Running it against the
<classname>Operator</classname> service from the
<link linkend="yar.examples">examples</link> prints
<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>
A more complete client implementation in plain PHP, which also
decodes the response header and supports concurrent calls, lives in
the <literal>tools/</literal> directory of the
<link xlink:href="&url.git.hub;laruence/yar">Yar source
repository</link>.
</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