diff --git a/reference/yar/book.xml b/reference/yar/book.xml index f75320892348..9aef1a42aa73 100644 --- a/reference/yar/book.xml +++ b/reference/yar/book.xml @@ -23,11 +23,13 @@ are required. - 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 The Yar Protocol for + the details. + &reference.yar.protocol; &reference.yar.setup; &reference.yar.constants; &reference.yar.examples; diff --git a/reference/yar/constants.xml b/reference/yar/constants.xml index 3845c58a289e..0a09192440b1 100644 --- a/reference/yar/constants.xml +++ b/reference/yar/constants.xml @@ -310,6 +310,20 @@ + + + YAR_ERR_FORBIDDEN + (int) + + + + The request was rejected by the server's authentication + (see the provider and + token fields of the + Yar protocol header). + + + diff --git a/reference/yar/examples.xml b/reference/yar/examples.xml index 4ec8acf9b320..42a99ca59fe7 100644 --- a/reference/yar/examples.xml +++ b/reference/yar/examples.xml @@ -3,13 +3,33 @@ &reftitle.examples; + + 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. + + Yar Server Example + + A Yar service is just a regular PHP class wrapped by + Yar_Server. 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. + + + 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. + handle(); Access the server in browser (GET request) - 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 - yar.expose_info - 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 yar.expose_info + directive; when it is off, a GET request fails instead. &example.outputs.similar; @@ -73,10 +94,22 @@ $server->handle(); Yar Client Example + + A Yar_Client 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; Yar_Client::call does the same + thing explicitly by name. + + + Protected methods are not exposed: calling one fails with a + Yar_Client_Exception whose code is + YAR_ERR_REQUEST. + add(1, 2)); @@ -94,17 +127,34 @@ var_dump($client->_add(1, 2)); Yar Concurrent Client Example + + Instead of calling services one after another, + Yar_Concurrent_Client registers several calls + first and then dispatches them all at once with + Yar_Concurrent_Client::loop. The responses + are passed to the callback in the order they arrive, not in the + order the calls were registered. + + + 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. + Besides HTTP, Yar_Client 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 + Yar C framework, + which serves the same binary Yar protocol that the PHP server uses. + + + + The Yar Protocol + + 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. + + + 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: + + + + + + The id, magic_num, + reserved and body_len fields + are stored in network byte order (big-endian); the remaining fields + are raw bytes. + + + The body starts with an 8-byte packager identifier — + PHP, JSON or + MSGPACK, zero-padded — telling the receiver how + the remainder was encoded, followed by the serialized content itself. + + + + + The request body decodes to an array with the keys + i (the transaction id), m + (the method being called) and p (the list of + parameters). + + + + + The response body decodes to an array with the keys + i (the transaction id), s + (the status, one of the YAR_ERR_* codes), + r (the return value), o (any + output the service method produced) and e (the + error or exception, when the call failed). + + + + + 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. + + + Calling a Yar service without the extension + + The following self-contained script builds a valid Yar request for + the php packager with nothing but standard + sockets, sends it to a service URI, and prints the decoded + response. Running it against the + Operator service from the + examples prints + int(3). + + + 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"]); +?> +]]> + + + + A more complete client implementation in plain PHP, which also + decodes the response header and supports concurrent calls, lives in + the tools/ directory of the + Yar source + repository. + + + + diff --git a/reference/yar/setup.xml b/reference/yar/setup.xml index 7035e5a05bb1..deb2031c1bf8 100644 --- a/reference/yar/setup.xml +++ b/reference/yar/setup.xml @@ -26,6 +26,10 @@
&reftitle.install; + + Yar can be installed in one of three ways: via PECL, via PIE, or + by building it from source. + &pecl.moved; @@ -36,66 +40,100 @@ &pecl.windows.download.avail; - + + Installing Yar with PECL + + + + + + As of Yar 2.4.0, the extension can be installed with + &link.pie;, the PHP Installer for Extensions, by running the + following on the command line. + + + Installing Yar with PIE + + + + + + The msgpack packager can be enabled at install time: + + + Installing Yar with PIE and msgpack + + + + + The source code is hosted on - GitHub. To build - the extension from source: - + GitHub. To + build the extension from source, run the following on the command + line, replacing the paths with those of the local PHP + installation. + + + Building Yar from source + - - - + + + The following configure options are available: - - - - - - - - Location of the cURL installation, if it is not found in the - default include paths. - - - - - - - - - - Enables the msgpack packager and makes the - msgpack extension an optional dependency. When - Yar is built with this option, the default value of - yar.packager becomes - msgpack. - - - - - - - - - - Uses Linux epoll instead of - select() for I/O multiplexing, available as of - Yar 2.1.2. This can improve - Yar_Concurrent_Client performance under - high concurrency. It only takes effect on Linux; on other - platforms it is silently ignored. - - - - - + + + + + + + + + Location of the cURL installation, if it is not found in the + default include paths. + + + + + + + + + + Enables the msgpack packager and makes the + msgpack extension an optional dependency. When + Yar is built with this option, the default value of + yar.packager becomes + msgpack. + + + + + + + + + + Uses Linux epoll instead of + select() for I/O multiplexing, available as of + Yar 2.1.2. This can improve + Yar_Concurrent_Client performance under + high concurrency. It only takes effect on Linux; on other + platforms it is silently ignored. + + + +
diff --git a/reference/yar/versions.xml b/reference/yar/versions.xml index 7868cdcc7b9a..9319614bc56e 100644 --- a/reference/yar/versions.xml +++ b/reference/yar/versions.xml @@ -7,7 +7,6 @@ - diff --git a/reference/yar/yar-concurrent-client.xml b/reference/yar/yar-concurrent-client.xml index fa6dedb85ea7..d484d1c54204 100644 --- a/reference/yar/yar-concurrent-client.xml +++ b/reference/yar/yar-concurrent-client.xml @@ -17,9 +17,21 @@ immediately; they are all dispatched together, in parallel, by Yar_Concurrent_Client::loop. + + The class is meant for applications that need the results of several + remote calls. Only independent calls — ones that do not need each + other's results — can be batched this way; when one call depends on + the result of another, the two have to run sequentially. Through + Yar_Client the calls are sent one after + another, each paying its full round-trip time; with the concurrent + client they are sent at the same time, so the overall waiting time + drops to the duration of the single slowest call. + - Only HTTP(S) services are supported for concurrent calls. + Only HTTP(S) services are supported for concurrent calls. They are + sent concurrently through curl's multi-handle interface, which the + TCP/Unix socket transport does not implement. diff --git a/reference/yar/yar-server.xml b/reference/yar/yar-server.xml index 1a7f1eb61a2d..4ffdc1203f36 100644 --- a/reference/yar/yar-server.xml +++ b/reference/yar/yar-server.xml @@ -16,6 +16,11 @@ service, served over HTTP by Yar_Server::handle. + + The PHP extension only provides an HTTP server. A TCP and Unix + socket server speaking the same Yar protocol is provided by the + Yar C framework. + diff --git a/reference/yar/yar_client/call.xml b/reference/yar/yar_client/call.xml index 702bfee10e0c..cfb5ccc8dc2d 100644 --- a/reference/yar/yar_client/call.xml +++ b/reference/yar/yar_client/call.xml @@ -17,8 +17,8 @@ Issues an RPC call to the remote method method. This is exactly what happens when a non-existent method is invoked on a - Yar_Client object (see - Yar_Client::__call); + Yar_Client object (via PHP's + __call magic method); Yar_Client::call only exists so that remote methods literally named call or __call can still be reached. @@ -57,17 +57,51 @@ &reftitle.errors; - When the call method does not exist on the server - side, a Yar_Server_Request_Exception is - thrown. See Yar_Client::__call for the full - list of failures and their exception classes. + When the remote method does not exist on the server, or is not + public, the client throws a + Yar_Client_Exception with the code + YAR_ERR_REQUEST. See + Yar_Client_Exception + for the other client-side failures and their exception classes. + + &reftitle.examples; + + <methodname>Yar_Client::call</methodname> example + +add(1, 2)); + +/* Equivalent: call the method explicitly by name */ +var_dump($client->call("add", array(1, 2))); + +/* Needed only when the remote service literally exposes a method + * named call (or __call), which the magic __call cannot reach */ +var_dump($client->call("call", array($some_argument))); +?> +]]> + + &example.outputs.similar; + + + + + + &reftitle.seealso; - Yar_Client::__call Yar_Client::setOpt diff --git a/reference/yar/yar_client/construct.xml b/reference/yar/yar_client/construct.xml index 003fc848a0f7..6e2bde9899fd 100644 --- a/reference/yar/yar_client/construct.xml +++ b/reference/yar/yar_client/construct.xml @@ -70,10 +70,10 @@ 1000, YAR_OPT_PACKAGER => "json", ]); @@ -86,7 +86,7 @@ $client = new Yar_Client("http://host/api/", [ &reftitle.seealso; - Yar_Client::__call + Yar_Client::call Yar_Client::setOpt diff --git a/reference/yar/yar_client/getopt.xml b/reference/yar/yar_client/getopt.xml index 152194e02ef2..b701db0ccbe0 100644 --- a/reference/yar/yar_client/getopt.xml +++ b/reference/yar/yar_client/getopt.xml @@ -51,7 +51,7 @@ setOpt(YAR_OPT_TIMEOUT, 1000); var_dump($client->getOpt(YAR_OPT_TIMEOUT)); diff --git a/reference/yar/yar_client/setopt.xml b/reference/yar/yar_client/setopt.xml index 233f30b07020..860c7776a5d3 100644 --- a/reference/yar/yar_client/setopt.xml +++ b/reference/yar/yar_client/setopt.xml @@ -102,7 +102,7 @@ setOpt(YAR_OPT_TIMEOUT, 1000); @@ -128,7 +128,7 @@ $result = $client->some_method("parameter"); &reftitle.seealso; Yar_Client::getOpt - Yar_Client::__call + Yar_Client::call diff --git a/reference/yar/yar_client_exception/gettype.xml b/reference/yar/yar_client_exception/gettype.xml index f7b4c050313b..9b664bb3b641 100644 --- a/reference/yar/yar_client_exception/gettype.xml +++ b/reference/yar/yar_client_exception/gettype.xml @@ -43,7 +43,7 @@ some_method("parameter"); diff --git a/reference/yar/yar_concurrent_client/call.xml b/reference/yar/yar_concurrent_client/call.xml index 5611b3920d4b..79d6fb89e5a0 100644 --- a/reference/yar/yar_concurrent_client/call.xml +++ b/reference/yar/yar_concurrent_client/call.xml @@ -26,8 +26,9 @@ - Only HTTP and HTTPS URIs are supported. TCP and Unix socket transports - are not available for concurrent calls. + Only HTTP and HTTPS URIs are supported. Concurrent calls are sent + simultaneously using curl's multi-handle interface, and the TCP/Unix + socket transport does not provide this facility. @@ -66,11 +67,18 @@ A callable invoked when the response for this call arrives, with two arguments: the response value, and a - callinfo &array; with the keys - sequence, uri and - method. If omitted, the - callback of - Yar_Concurrent_Client::loop is used. + callinfo &array; describing the call: + + + sequence — the sequence number returned + when the call was registered + uri — the address of the service + method — the name of the remote method + + + If omitted, the callback of + Yar_Concurrent_Client::loop is used; + see that method for what happens when neither is given. @@ -81,8 +89,10 @@ A callable invoked when this call fails, with three arguments: the error type (one of the YAR_ERR_* codes), the error message, and the callinfo - &array;. If omitted, the error_callback of - Yar_Concurrent_Client::loop is used. + &array; described above. If omitted, the + error_callback of + Yar_Concurrent_Client::loop is used; + see that method for what happens when neither is given. @@ -130,16 +140,16 @@ function error_callback($type, $error, $callinfo) error_log($error); } -Yar_Concurrent_Client::call("http://host/api/", "some_method", array("parameters"), "callback"); +Yar_Concurrent_Client::call("http://api.example.com/operator.php", "some_method", array("parameters"), "callback"); /* If the callback is not specified, the callback of loop() will be used */ -Yar_Concurrent_Client::call("http://host/api/", "some_method", array("parameters")); +Yar_Concurrent_Client::call("http://api.example.com/operator.php", "some_method", array("parameters")); /* This server accepts the JSON packager */ -Yar_Concurrent_Client::call("http://host/api/", "some_method", array("parameters"), "callback", NULL, array(YAR_OPT_PACKAGER => "json")); +Yar_Concurrent_Client::call("http://api.example.com/operator.php", "some_method", array("parameters"), "callback", NULL, array(YAR_OPT_PACKAGER => "json")); /* Custom timeout */ -Yar_Concurrent_Client::call("http://host/api/", "some_method", array("parameters"), "callback", NULL, array(YAR_OPT_TIMEOUT => 1000)); +Yar_Concurrent_Client::call("http://api.example.com/operator.php", "some_method", array("parameters"), "callback", NULL, array(YAR_OPT_TIMEOUT => 1000)); /* The requests are not sent yet */ ]]> diff --git a/reference/yar/yar_concurrent_client/loop.xml b/reference/yar/yar_concurrent_client/loop.xml index ad4b5c5bafac..8376bc566d4f 100644 --- a/reference/yar/yar_concurrent_client/loop.xml +++ b/reference/yar/yar_concurrent_client/loop.xml @@ -21,6 +21,20 @@ blocks until every response has arrived and been handled. The call list is emptied when this method returns. + + Because the calls run simultaneously, the loop takes only as long as + the slowest single call rather than the sum of all calls. The + callbacks, however, are not invoked in the order the calls were + registered: a callback fires as soon as its response arrives. To find + out which call a response belongs to, check the + sequence entry of the callinfo + argument against the sequence number returned by + Yar_Concurrent_Client::call. The + callinfo &array; holds the + sequence, uri and + method of the call; see + Yar_Concurrent_Client::call. + @@ -35,6 +49,10 @@ been sent, it is additionally called once with two &null; arguments, before any response arrives. + + If this parameter is omitted, the return value of a successful call + is simply printed when its response arrives. + @@ -47,6 +65,11 @@ codes), the error message, and the callinfo &array;. + + If this parameter is omitted, a failed call raises a PHP warning + instead; unlike the synchronous + Yar_Client, no exception is thrown. + @@ -93,10 +116,10 @@ function error_callback($type, $error, $callinfo) { error_log($error); } -Yar_Concurrent_Client::call("http://host/api/", "some_method", array("parameters"), "callback"); +Yar_Concurrent_Client::call("http://api.example.com/operator.php", "some_method", array("parameters"), "callback"); /* if the callback is not specified, the callback of loop() will be used */ -Yar_Concurrent_Client::call("http://host/api/", "some_method", array("parameters")); +Yar_Concurrent_Client::call("http://api.example.com/operator.php", "some_method", array("parameters")); Yar_Concurrent_Client::loop("callback", "error_callback"); ?> diff --git a/reference/yar/yar_concurrent_client/reset.xml b/reference/yar/yar_concurrent_client/reset.xml index 1c3d160d55ad..c05960ff6f98 100644 --- a/reference/yar/yar_concurrent_client/reset.xml +++ b/reference/yar/yar_concurrent_client/reset.xml @@ -52,7 +52,7 @@ function callback($retval, $callinfo) { var_dump($retval); } -Yar_Concurrent_Client::call("http://host/api/", "some_method", array("parameters"), "callback"); +Yar_Concurrent_Client::call("http://api.example.com/operator.php", "some_method", array("parameters"), "callback"); /* never sent: throw the registered call away */ Yar_Concurrent_Client::reset(); diff --git a/reference/yar/yar_server/construct.xml b/reference/yar/yar_server/construct.xml index 1b2992cc7ba9..c8cd795f3a78 100644 --- a/reference/yar/yar_server/construct.xml +++ b/reference/yar/yar_server/construct.xml @@ -30,6 +30,42 @@ services. Protected and private methods, and methods whose name starts with an underscore, are not exposed. + + The executor can optionally define two protected magic methods, + which are recognized by + Yar_Server::handle and never exposed as + RPC endpoints: + + + + + protected function __info(string $markup): + string — as of Yar 2.3.0. Called when the service + information page is requested; it receives the markup of the + page Yar would otherwise render, and if it returns a &string;, + that string is sent to the client instead. See + Yar_Server::handle. + + + + + protected function __auth(string $provider, string + $token): bool — as of Yar 2.3.0. Called at the very + beginning of every request with the + provider and token + fields of the request header, set by the client with the + YAR_OPT_PROVIDER and + YAR_OPT_TOKEN options. Returning exactly + &false; rejects the request; any other return value lets it + proceed. See Yar_Server::handle. + + + + + Both methods only take effect when they are + protected; a public or private method with + the same name is ignored. + diff --git a/reference/yar/yar_server/handle.xml b/reference/yar/yar_server/handle.xml index 81d05acfc003..feaa5ef686c5 100644 --- a/reference/yar/yar_server/handle.xml +++ b/reference/yar/yar_server/handle.xml @@ -29,12 +29,11 @@ - As of Yar 2.3.0, the service information page can be customized by - defining a protected method named - __info on the executor object. When a GET request - arrives, Yar calls this method, passing it the markup of the page it - would otherwise render; if the method returns a &string;, that string - is sent to the client instead of the default page. + As of Yar 2.3.0, the executor object may define the protected + magic methods __info and + __auth to customize the service information + page and to authenticate requests; see + Yar_Server::__construct for details. diff --git a/reference/yar/yar_server_exception/gettype.xml b/reference/yar/yar_server_exception/gettype.xml index 3a0d8bcb38ea..e9edbdbad86d 100644 --- a/reference/yar/yar_server_exception/gettype.xml +++ b/reference/yar/yar_server_exception/gettype.xml @@ -60,7 +60,7 @@ $service->handle(); throw_exception("client");