NAME
Rinci::HTTP - Request metada and perform actions on code entities over HTTP
VERSION
version 1.1.0
SPECIFICATION VERSION
1.1
ABSTRACT
This document specifies a protocol for requesting metadata and performing actions on code entities over HTTP. The protocol can be used for REST/HTTP API service.
Examples are written in JSON (sometimes with added comments).
INTRODUCTION
Rinci::HTTP leverages the metadata specified by Rinci and provides a mechanism to request them over HTTP. Aside from that, you can also perform actions on code entities. For example, for functions these actions are available: call
, and complete
(to complete function arguments). Requesting metadata, as previously mentioned, is also done by performing an action: get_meta
.
Rinci::HTTP also requires the implementors to follow a few conventions which have the benefits of common API access. You can have a single API client library for all websites providing API service, including for documentation (since they are contained in the metadata), as long as the websites are Rinci::HTTP-compliant. This is despite the fact that the websites can keep their own different preferred URL schemes.
Rinci::HTTP also defines some extra features like passing logging messages from the server (for debugging).
Lastly, it is to worth noting that Rinci::HTTP is actually just a simple request/response protocol utilizing HTTP as its transport layer. It can as well as use other protocols in the future.
TERMINOLOGIES
Server
A server is an HTTP service (e.g. a web application or middleware running on top of a webserver), listening for HTTP requests. Its responsibility is to accept HTTP request, extract/parse Rinci request from the HTTP request, process the Rinci request, and return an enveloped result inside the HTTP response.
Rinci request
Rinci request is a hash containing at least these keys: v which is the Rinci::HTTP protocol version (1.1), uri which is the URI to code entity, and action which is the action to perform on the code entity. ofmt (optional, which is the desired output format. Some actions require more arguments, put in the other keys of the Rinci request. The specification requires the server to implement a minimal set of actions. The server MUST also support
json
as one of the output formats.Enveloped result
Enveloped result is as defined by the Rinci specification. It is basically a simple HTTP-message-like wrapper to the actual result. For example, instead of returning just
42
, you return[200, "OK", 42]
. Envelopes can carry error code/message as well as extra metadata along with the actual result.Client
A client can be any normal bare HTTP client (like the curl or wget program), or some library which understands Rinci HTTP protocol and can provide additional abstraction and convenience. For example, the Sub::Spec::Use Perl module can provide the illusion of "loading" remote modules and functions from a Rinci HTTP server as if they were local. The Sub::Spec::HTTP::Client::CLI command-line client allows users to access server documentation, do tab completion, and more.
RINCI REQUEST
As mentioned previously, a Rinci request is a hash containing at least these keys: v (protocol version), uri (syntax is explained in the Rinci specification, the ent:
scheme is optional except for the top namespace: ent:
) and action. Depending on the action, more keys might be required. The server SHOULD return an HTTP 400 error code if client misses a required key or sends an unknown key or sends a key with invalid value. The server SHOULD return HTTP 502 if protocol version is not supported. The server SHOULD return HTTP 404 if uri does not map to an existing code entity. The server SHOULD return HTTP 502 error if an action is unknown for the specified URI. The server SHOULD return HTTP 401/403 if authentication is required or action is not allowed for the specified code entity, respectively.
The server is viewed as containing a tree of namespaces/packages, starting from the top namespace ent:
. Other entities, including subpackages, live inside the namespace entities. Discovering entities is done by performing a list
action on the namespace entities.
Common actions
Below are the actions which must be implemented by the server for all kinds of entities.
Action: info
Get general information and information about the code entity. This action requires no additional Rinci 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):
{
// server's protocol version
"v": 1.1,
// entity's canonical URL
"url": "http://localhost:5000/api/Package/SubPkg/func",
// entity's type
"type": "function",
// actions available for this entity
"acts": ["info", "call", "complete"],
"defact": "call",
// available input formats for HTTP request body,
// for value of C<args> request key
"ifmt": ["json", "yaml", "phps"],
// available output formats
"ofmt": ["json", "yaml", "phps", "text", "html"],
// server base URL
"srvurl": "http://localhost:5000/api/"
}
The server may add additional information.
Actions for package
entities
Below are actions that must be supported by the package
entities:
Action: list
List entities contained in this package. Additional Rinci 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).
The server should return 200 status or 206 if partial list is returned. The server must return entries in array, with each element a hash containing at least uri and type. Server may add additional information like summary, description, etc.
Example, a list
action on the top namespace ent:
might return the following:
[200,"OK",
[
// first entity
{"uri": "Math", "type": "package", "summary": "Contain math functions"},
// second entity
{"uri": "Utils", "type": "package", "summary": "Contain utility functions"},
// and so on
// ...
]
]
Another example, a list
action on the Math
namespace, with type
set to function
and q
to multiply
:
[200,"OK",
[
{"uri": "Math.multiply2",
"type": "function",
"summary": "Multiply two numbers"},
{"uri": "Math.multmany",
"type": "function",
"summary": "Multiply several numbers"}
]
]
Action: meta
Return Rinci metadata for the code entity. When the entity does not have metadata, server should return HTTP 534 (metadata not found) status.
Actions for function
entities
Below are actions that are available for the function
entities. At least call
must be implemented by the server.
Action: call
Call a function and return its result. Additional Rinci request keys include:
args
Hash, optional, function arguments, defaults to
{}
.loglevel
An integer number with value either 0 (for none, the default), 1 (for sending fatal messages), 2 (error), 3 (warn), 4 (info), 5 (debug), and 6 (trace). When a value larger than 0 specified, server must return chunked HTTP response and each log message should be sent as a separate chunk, and the result as the last chunk.
marklog
A bool, default to 0. When set to true, server will prepend each log message with "L" (and the result with "R"). Only useful/relevant when turning on loglevel, so clients can parse/separate log message from result.
Action: complete
Complete function argument value, a la Bash tab completion where you have a semicompleted word and request possible values started by that word. Additional Rinci 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. Example, when completing a delete_user
function for the argument username
, and word
is "st", the server might return:
[200,"OK",["stella","steven","stuart"]]
When there is no completion, the server should return an empty list:
[200,"OK",[]]
RINCI SERVER
Server listens to HTTP requests, parses them into Rinci requests, executes the Rinci requests, and send the result to client.
Parsing Rinci request from HTTP request
Server can provide defaults for some/all Rinci request keys, so client does not need to explicitly set a Rinci request key. But server MUST provide a way for client to set Rinci request key.
First, server MUST parse Rinci request keys from HTTP X-Ri-*
request headers, e.g. X-Ri-Action
header for setting the action
request key. In addition, the server MUST parse X-Ri-*-j-
for JSON-encoded value, e.g.
X-Ri-Args-j-: {"arg1":"val1","arg2":[1,2,3]}
The server MUST also accept args
from request body. The server MUST accept at least body of type application/json
. It can accept additional types if it wants, e.g. text/yaml
or application/vnd.php.serialized
.
The server can also accept Rinci request keys or function arguments using other means, for example, Serabi allows parsing uri
from URI path, and function arguments (as well as other Rinci request keys, using -ri-*
syntax) from request variables. For example:
http://HOST/api/PKG/SUBPKG/FUN?a1=1&a2:j=[1,%202]
will result in the following Rinci request:
{
"uri": 'ent:PKG.SUBPKG.FUN',
"action": 'call',
"args": {"a1":1, "a2":[1,2]},
}
Another example:
http://HOST/api/PKG/FUN?-ri-action=complete&-ri-arg=a1&-ri-word=x
will result in the following Rinci request:
{
"uri": 'ent:PKG.FUN',
"action": 'complete',
"arg": 'a1',
"word": 'x',
}
EXAMPLES
Below are some examples of what is sent and received on the wire. For these examples, the server has the following URL scheme http://example.org/api/v1/<ENTITY_URI>. Entity URI can be written using path syntax, where the server will convert it. It detects desired output format from the Accept
HTTP request header.
Call a function, passing function arguments via query parameter, unsuccessfully because of missing argument:
--- Request ---
GET /api/v1/Math/multiply2?a=2 HTTP/1.0
Accept: application/json
--- Response ---
HTTP/1.0 200 OK
Date: Sat, 14 Jan 2012 17:11:40 GMT
Server: Serabi/1.0
Content-Type: application/json
[400,"Missing required argument: b"]
Call the same function, successfully this time. As a variation we pass function arguments through the X-Ri-Args HTTP header:
--- Request ---
GET /api/v1/Math/multiply2 HTTP/1.0
X-Ri-Args-j-: {"a":2,"b":3}
Accept: application/json
--- Response ---
HTTP/1.0 200 OK
Date: Sat, 14 Jan 2012 17:11:50 GMT
Server: Serabi/1.0
Content-Type: application/json
[200,"OK",6]
FAQ
Why not directly return status from enveloped result as HTTP response status?
Since enveloped result is modeled somewhat closely after HTTP message, especially the status code, it might make sense to use the status code directly as HTTP status. But this means losing the ability to differentiate between the two. We want the client to be able to differentiate whether the 500 (Internal server error) or 404 (Not found) code it is getting is from the HTTP or from the enveloped result.
Why no actions to modify metadata/code entities?
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 API functions.
There are 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. 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?
HISTORY
1.1 (Jan 2012)
Rename specification to Rinci::HTTP. Version bumped to 1.1 to various backward-incompatible adjustments to Rinci's terminologies.
1.0 (Aug 2011)
Slit specification to Sub::Spec::HTTP.
May 2011
First release of Sub::Spec::HTTP::Server.
SEE ALSO
AUTHOR
Steven Haryanto <stevenharyanto@gmail.com>
COPYRIGHT AND LICENSE
This software is copyright (c) 2012 by Steven Haryanto.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.