Riap - Rinci access protocol
This document describes version 1.2.4 of Riap (from Perl distribution Riap), released on 2015-09-04.
This document specifies a simple, extensible, client/server, request/response protocol for requesting metadata and performing actions on code entities.
Examples are written in JSON (sometimes with added comments), but data structures can actually be encoded using other formats.
Rinci access protocol (Riap for short), is a client/server, request/response protocol for requesting metadata and performing actions on code entities. It is modeled closely after HTTP, but is a different protocol. It can be layered on top of HTTP (as its transport protocol) but can also use other transports, including direct TCP.
The server side is viewed as being a tree of code entities, with a package entity at the root. Other entities (such as subpackages, functions, variables) are discoverable by performing list actions on package entities. Entity's metadata can be retrieved using the meta action. There are other actions defined in the specification; and the protocol can be extended by introducing more actions.
list
meta
One of the main use-cases of this protocol is to provide self-descriptive, self-documenting, machine-discoverable API service, much like the goal of SOAP and WSDL. However it is much simpler and programmer-friendly than those protocols.
1.2
The 1.2 series does not guarantee full backward compatibility between revisions, so caveat implementor. However, major incompatibility will bump the version to 1.3 or 2.0.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Server
Client
Response
Response is an enveloped result as defined in the Rinci::function specification.
Request
A Riap request is modelled after HTTP request. It consists of an action (analogous to HTTP request method), code entity URI, protocol version, and zero or more extra arguments (analogous to HTTP headers). Some extra arguments might be required, depending on the action.
For simplicity, the request can be expressed as a hash (or dictionary) of key/value pairs. There should at least be these keys: action, uri, and v (for protocol version). This also means the extra arguments cannot have those names. They must also start with letters or underscores followed by zero or more letters/underscores/digits.
action
uri
v
Server should return 400 status if required keys are missing, unknown keys are sent, or keys contain invalid values.
To refer to code entities on the Riap server, a new URI scheme riap is defined. The URI path describes path to code entities while the URI host describes the host language. Examples:
riap
# refer to Text::sprintfn Perl module riap://perl/Text/sprintfn/ # refer to sprintf function in Text::sprintfn Perl module riap://perl/Text/sprintfn/sprintfn # refer to PHP variable riap://php/$var # UNDECIDED: refer to class metadata riap://perl/My/Class/:class # UNDECIDED: refer to distribution metadata riap://perl/My/Dist/:distribution
There are some other schemes recognized.
pl. The pl hostless scheme refers to code entities in Perl. This is preferred in many Perinci modules because the riap scheme is more verbose. Examples:
pl
# refer to Text::sprintfn Perl module pl:/Text/sprintfn/ # refer to sprintf function in Text::sprintfn Perl module pl:/Text/sprintfn/sprintfn
riap+tcp. This scheme is for Riap::Simple over TCP socket. The host (+port) part describes TCP host (+port), and the path part describes path to code entity. Currently there is no default port so please always specify port number. Examples:
riap+tcp://localhost:5000/Text/sprintfn/ riap+tcp://localhost:5000/Text/sprintfn/sprintfn
riap+unix. This scheme is for Riap::Simple over Unix socket. There is no host part (Unix socket is localhost-only). The path part describes path to the socket and path to code entity (separated by //).
//
riap+unix:/path/to/unix/socket riap+unix:/path/to/unix/socket//Text/sprintfn/sprintfn
riap+pipe. This scheme is for Riap::Simple over pipe, to refer to starting a program and talking the protocol over the pipe. There is no host. The path part describes path to program, // separator, arguments separated by /, //separator, and path to code entity.
/
//separator
# refer to accessing code entity via executing "/path/to/program" riap+pipe:/path/to/program////Text/sprintfn/sprintfn # refer to via accessing code entity via executing "/path/to/program arg1 arg2" riap+pipe:/path/to/program//arg1/arg2//Text/sprintfn/sprintfn
For Riap over HTTP/HTTPS as the transport layer, the standard URI scheme http/https is used. The URI path for these do not necessarily map directly to code entities like in riap or pl URIs, they may also support additional features, as each service provider can choose custom URL layout. Example:
# access tax::id::validate_npwp function, call with arguments http://gudangapi.com/ga/tax.id.npwp/validate_npwp?npwp=123456 # namespace/module can also be omitted when function name is unique. arguments # can also be specified by position http://gudangapi.com/ga/validate_npwp/123456
However, any Riap request and any code entity URI can still be requested from any http/https URL following the Riap::HTTP protocol..
As mentioned previously, the Riap request is a mapping of request keys and their values. Common request keys:
v => FLOAT
Specify Riap protocol version. If not specified, default is 1.1. Server should return 501 status if it does not support requested protocol version.
1.1
uri => STR
Required. Specify the location to code entity. It is either a schemeless URI (e.g. /Package/SubPackage/func) to refer to "local" code entity or URI with a scheme to refer to a remote entity, e.g. http://example.org/api/Foo/Bar/func, in which case the server can decide to proxy it for the client or not.
/Package/SubPackage/func
http://example.org/api/Foo/Bar/func
The server should return 404 status if uri does not map to an existing code entity, or 403 if uri is forbidden.
action => STR
Required. Specify action to perform on the code entity.
The server should return 501 status if an action is unknown for the specified URI. The server should return 401/403 status if authentication is required or action is not allowed for the specified code entity, respectively.
tx_id => STR
Optional. Transaction ID. Only needed if you want to execute request inside transaction scope. See Riap::Transaction for more details.
Additional keys might be recognized and/or required according to the action.
Below are the actions which can be implemented by the server. Server can choose to not implement any action, or implement additional actions. But for actions mentioned below, the specification must be followed.
Get general information and information about the code entity. This action requires no additional request keys. Upon success, the server must return a hash result with at least the following keys (remember that the result is actually enveloped with a 200 status):
[200,"OK", { // entity's type "type": "function", // canonical URI for the entity "uri": "/Package/SubPkg/", }, {"riap.v": 1.2} ]
Server may add additional information keys.
List available actions for code entity. The server should return a list of action names:
[200,"OK", ["info","actions","meta","call","complete_arg_val"], {"riap.v":1.2} ]
Additional request key: detail (bool, optional, default false, can be set to true to make server return a list of records instead).
Under detail, server should return something like this:
[200,"OK", [ {"name":"info", "summary":"Get general information about code entity"}, {"name":"actions","summary":"List available actions for code entity"}, {"name":"meta","summary":"Get metadata for code entity"}, {"name":"call","summary":"Call function"}, {"name":"complete_arg_val","summary":"Complete function's argument value"} ], {"riap.v":1.2} ]
It can return additional field like keys to explain additional required/optional request keys (XXX not yet specified).
keys
Return Rinci metadata for the code entity. When the entity does not have metadata, server should return 404 status or better yet 534 (metadata not found).
package
Below are actions that must be supported by the package entities.
List entities contained in this package. Additional request keys are: type (string, optional, to limit only listing entities of a certain type; default is undef which means list all kinds of entities), recursive (bool, optional, can be set to true to search subpackages; default is false which means only list entities in this namespace), q (string, search terms, to only return matching some search terms; default is undef which means return all entities), detail (bool, optional, whether to return just a list of code entity URIs or a detailed record for each entry, defaults to false; if true, then server must return info hash for each entry, where each info hash like that returned by the info action).
info
The server should return 200 status or 206 if partial list is returned. If detail is true, for each entry a hash must be returned containing at least uri and type. Server may add additional information like summary, description, etc.
Example, a list action on the top namespace / might return the following:
[200,"OK", ["Math/","Utils/"], {"riap.v":1.2} ]
Another example, a list action on the pl:/Math namespace, with type set to function and q to multiply, and detail set to true:
pl:/Math
type
function
q
multiply
detail
[200,"OK", [ {"uri": "multiply2", "type": "function", "summary": "Multiply two numbers"}, {"uri": "multmany", "type": "function", "summary": "Multiply several numbers"} ], {"riap.v":1.2} ]
An abs_uri can also be provided by server for each record.
abs_uri
Get metadata for all the children entities of the package entity.
This action can reduce the number of round-trips, as opposed to client performing list action followed by meta for each child.
Example, an child_metas action on the top namespace / might return the following:
child_metas
[200,"OK", { "Math/": {"v":1.1, "summary":"This is metadata for Math"}, ,"Utils/": {"v":1.1, "summary":"This is metadata for Utils"} }, {"riap.v":1.2} ]
Below are actions that are available for the function entities. At least call must be implemented by the server.
call
Call a function and return its result. Additional request keys include:
args
Hash, optional, function arguments, defaults to {}.
{}
In general, do not pass special arguments (arguments prefixed by dash -) as they might be stripped/removed prior to processing. Some special arguments can be passed through other means (e.g. see confirm and dry_run below).
-
confirm
dry_run
confirm => BOOL
Optional. If set to true, will pass special argument -confirm => 1 to function. This is used as confirmation when function can return status 331 (requires confirmation). See Rinci::function for more details on status 331.
-confirm => 1
dry_run => BOOL
Optional. If set to true, will either pass -dry_run => 1 to function supporting dry run, or -tx_action => 'check_state' to function supporting transaction (can more or less be regarded as the dry run equivalent).
-dry_run => 1
-tx_action => 'check_state'
arg_len => int
Will be passed to function as special argument -arg_len to send partial argument value.
-arg_len
arg_part_start => int
Will be passed to function as special argument -arg_part_start to send partial argument value.
-arg_part_start
art_part_len => int
Will be passed to function as special argument -arg_part_len to send partial argument value.
-arg_part_len
res_part_start => int
Will be passed to function as special argument -res_part_start to request partial result.
-res_part_start
res_part_len => int
Will be passed to function as special argument -res_part_len to request partial result.
-res_part_len
stream_arg => bool
Optional. If set to true, then will provide a filehandle or an iterable object as argument value for the function, where the function can read data from. And will also send special argument -arg_stream => 1 to function. Note that the function must have exactly a single argument that has its streaming property set to true. Otherwise, server MUST send 501 (unimplemented) response.
-arg_stream => 1
streaming
Complete function argument value, a la Bash tab completion where you have a semicompleted word and request possible values started by that word. Additional Riap request keys include:
arg
String, required, the name of function argument to complete.
word
String, optional, word that needs to be completed. Defaults to empty string.
The server should return a list of possible completions. Example, when completing a delete_user function for the argument username, and word is "st", the server might return:
delete_user
username
[200,"OK", ["stella","steven","stuart"], {"riap.v":1.2} ]
When there is no completion, the server should return an empty list:
[200,"OK", [], {"riap.v":1.2} ]
This is just like complete_arg_val, except this tries to complete the value of an element of an array argument, instead of the value of the argument itself. This is useful for example when completing this command-line:
complete_arg_val
% somecmd --category foo --category <tab>
where category is argument of type array and its element contain some restricted value. The command-line option --category can be specified multiple times to set the array elements.
category
--category
index
Integer, required, which element to complete for (starts from 0 for the first element).
variable
Below are actions that are available for the variable entities.
Get variable value.
Since the specification is extensible by adding more actions, you can implement this on your system. These actions are not specified by this specification because currently the main goal of the protocol is to provide API service and read-only access to the metadata.
Alternatively, modifying metada/code entities can be implemented using calls to functions on the server which can perform the modifications.
There are also some issues which need to be considered when adding these actions. First of all, security. Second, you need to decide whether to modify the running/in-memory copy or the actual source code/files (as the code entities are usually stored as). When modifying the in-memory copy, the server-side architecture may have multiple copies (multiple processes and machines). Do you want to modify all those copies or just one the one process?
Riap stands for Rinci access protocol, but it is also an Indonesian word meaning: to gain in size or number.
Server must return an enveloped response as specified in Rinci::function specification, namely a 4-element array: [STATUS, MESSAGE, RESULT, META]. STATUS is an integer, MESSAGE is a string, RESULT is of any type and is the actual result, and META is a hash.
[STATUS, MESSAGE, RESULT, META]
STATUS
MESSAGE
RESULT
META
Starting from Riap 1.2, server must return META which contains at least riap.v key, containing the Riap protocol version. Client must recognize and remove all riap.* keys from the response before returning the final result to user. If there is an unknown riap.* keys or an unrecognized value, it must fail by returning 501 status to user. This is to handle/detect incompatibilities/new features in future versions.
riap.v
riap.*
Version bumped to 1.2. This version addresses sending/receiving binary data over JSON. In this version, client can send base64-encoded binary data in arguments using ARGNAME:base64 key. Server can return base64-encoded binary data in result, this is hinted by the riap.result_encoding attribute in result medatata. To detect/handle incompatibilities in future versions, client now must check riap.v and all riap.* properties in result metadata, and fail if it can't recognize an attribute.
ARGNAME:base64
riap.result_encoding
Rename specification to Riap. Version bumped to 1.1 to various backward-incompatible adjustments to Rinci's terminologies.
Split specification to Sub::Spec::HTTP.
First release of Sub::Spec::HTTP::Server.
Rinci
JSON RPC, (version 1 at http://json-rpc.org, version 2 at http://jsonrpc.org). First developed 2005, major revision to version 2 in 2009. Similar to Riap in several aspects: 1) request and response are a single hash (JSON objects); 2) transport-independent. Differences to Riap: 1) JSON RPC has a narrower scope, it only deals with RPC/method calls and the other things like metadata access/exchange, etc; 2) JSON RPC is tied to the JSON format, while Riap is serialization-format-independent; 3) JSON RPC mandates a request ID ("id") on every request, to allow pipelining (i.e. client sends an array of requests at once, answers will come potentially out of order). This could be added later to the Riap protocol when necessary.
SPORE, https://github.com/SPORE/specifications/blob/master/spore_description.pod.
SOAP, WSDL. Popular in the early 2000's, with similar goals (easier service discovery, "simple" request/response protocol which can utilize HTTP or other transport layer). Fell out of favor along with the rise of JavaScript and REST and increased criticism against the complexity of XML. Which is ironic because SOAP was originally created to be the simpler alternative to CORBA.
CORBA.
Please visit the project's homepage at https://metacpan.org/release/Riap.
Source repository is at https://github.com/perlancar/perl-Riap.
Please report any bugs or feature requests on the bugtracker website https://rt.cpan.org/Public/Dist/Display.html?Name=Riap
When submitting a bug or request, please include a test-file or a patch to an existing test-file that illustrates the bug or desired feature.
perlancar <perlancar@cpan.org>
This software is copyright (c) 2015 by perlancar@cpan.org.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
To install Riap, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Riap
CPAN shell
perl -MCPAN -e shell install Riap
For more information on module installation, please visit the detailed CPAN module installation guide.