Next: lib-simple-jsonrpc, Previous: lib-json, Up: The Prolog Library [Contents][Index]
library('jsonrpc/jsonrpc_server')
This library implements a generic JSON-RPC server where the requests are handled by user-supplied hooks.
This library should be considered pre-release, in that it may evolve, also incompatibly, as we gain more experience with how it is used.
Please note: You should always take the security implications into account if exposing any service to untrusted clients, e.g. on Internet.
The server can only handle one client connection, i.e. it does not provide any facility for multiplexing between different client streams.
The server implements both “ordinary” JSON-RPC messages, dispatched to the user-supplied “request hook”, and “Prolog-style” messages (which provide things like multiple solutions, backtracking, failure and cut), dispatched to the user supplied “call hook”.
See Simple JSON-RPC Server for a simplified way to use this module. Its documentation also contains examples.
The basic structure of the server is a loop that repeatedly reads one request and lets the user-supplied hook (or hooks) handle it, until the hook tells the server to return from the loop (or a fatal error occurs).
A user-specified state is threaded through the loop and the hooks,
so the hooks can use and modify the state as they process the
incoming requests. The state can be any non-variable term, it does
not have to be representable as JSON. For instance, the state
could contain constrained variables (as used by
library(clpfd)
).
• Responses |
For our purposes, a JSON-RPC request object (henceforth request) is a JSON (https://json.org) object, with the following members:
jsonrpc
A string with value "2.0"
.
id
An integer identifier established by the client for referring to this request. Identifiers should be unique, although this is not currently a strict requirement.
If the identifier is absent, this indicates a special kind of request, a “notification”, and a variable will be used. Notifications are currently not implemented.
method
A string naming the method to invoke.
params
An optional object or array with parameters to the method. If not supplied, the empty list will be used.
other members are allowed, but will be ignored.
An incoming request is represented as a request term
request(Method,Id,Params,RPC)
where each argument is the
Prolog representation of the corresponding JSON value (see
library(json)
for details), and RPC is the
representation of the entire request.
When a request comes in from the client, the request hook will be called with four arguments:
This is the request term as described above.
On success, the request hook should bind this to a value that specifies how the server should continue.
result(JSONValue)
This is the common case, and tells the server to reply to the
client with the JSONValue
as the result. The value should
use the representation of the JSON library. If no specific result
value is desired, you can specify the JSON null,
i.e. @(null)
.
quit(JSONValue)
This is like result(JSONValue)
but also tells the server to
quit, i.e. the server loop will return to the caller.
error(ErrorCode, Message)
Replies to the client with an error response with the specified error code and error message (a string). The error code must be an integer outside the range reserved by this library and by the JSON-RPC specification, see Responses for details. The Message should be a short string that briefly describes the error.
Please note: this is not considered an error (or failure) by the server itself, i.e. any remaining processing of the request will proceed as usual. In particular the request will be considered handled by the request hook, and no fallback processing will happen.
This is an input argument specifying the current state. The initial state is specified when the server is started. You can use any non-variable term as state, it does not have to be a value that can be represented as JSON.
This is an output argument specifying the new state to use for subsequent requests.
If the request hook fails, the server will do its default processing, which includes handling the “Prolog-style” messages. For this reason it is important that the request hook quietly fails for any method it does not recognize.
If a call hook was specified, it will be used for handling the
special methods ‘call’ and ‘once’ (unless the request
hook decides to handle these methods in some other way). The
params
member of these messages specifies how to create a
Prolog term that will then be passed to the call hook.
The params
member can specify the term in two different
ways, either directly using a name and a list (JSON array) of
arguments, or indirectly using a string with text in Prolog syntax
which will be read to create the corresponding term.
The params
should be one of:
[F|Args]
. A JSON array with one or more arguments, orname
with value F and args
with value Args.The value F should be a string denoting the name of the
callable term, and the Args should be an array of the
zero or more arguments. The term will be created similarly to
Term =.. [F|Args]
.
read
and (optionally) bindings
.The value of the read
member should be a string in Prolog
syntax, including a final full stop, and the term will be created
by reading from this string.
If bindings
is supplied, it should be an object where the
value of each member will be used for providing an initial value
of the variable with the same name (if any) in the
term. Extraneous members are ignored.
The variable names and their values will be recorded when reading
the term, as with the read_term/3
option
variable_names/1
. This value will be passed to the call
hook (for other ways of specifying the term, the variables will be
an empty list).
Once the term has been created, it is passed, together with the list of named variables, to the call hook, similarly to how the request hook is invoked, but with some special considerations for handling backtracking and cut.
When a Prolog-style request comes in from the client, the call hook will be called with five arguments:
This is the request term as described above.
This is the list of variable names and values for the term, if the term was read from a string (with any bindings specified in the request already applied). This can be used for giving special meaning to variables that occur deep within the term, without the server code needing to know the exact structure of the term that the client sent.
On success, the request hook should bind this to a value that specifies how the server should continue.
result(JSONValue)
This is the same as for the request hook.
quit(JSONValue)
Not supported. Unlike the request hook, a call hook cannot tell the server to quit.
error(ErrorCode, Message)
This is the same as for the request hook.
Please note: this is not considered an error (or failure)
by the server itself, i.e. any remaining processing of the
request will proceed as usual. In particular the call
request will still be active and must be closed with
cut
or retry
.
This is an input argument specifying the current state. The initial state is specified when the server is started. You can use any non-variable term as state, it does not have to be a value that can be represented as JSON.
This is an output argument specifying the new state to use for subsequent requests.
If the call hook fails, possibly after a control message, this will be reported back to the client as a failure response (see Responses, below).
If the incoming request specifies the method once
, a term
is created as above, and the call hook will be called as if by
once/1
, i.e. it can succeed or fail (or throw an error),
but it cannot leave any choice points behind.
If the call hook succeeds, the response will be as specified by the ResultDescription argument. If the call fails, a failure response will be sent back to the client.
In most cases an ordinary request, handled by the request hook, is preferable, unless the additional ways to specify the term is required, or it is important to be able to report failure as distinct from unhandled.
If the incoming request specifies the method call
, a term
is created as above, and the call hook will be called as if by
call/1
, i.e. it can succeed or fail (or throw an error),
and it can leave choice points behind.
If the call hook succeeds, it is becomes active, and the response will be as specified by the ResultDescription argument. If the call fails, the call will not become active, and a failure response will be sent back to the client.
An active
call corresponds to a Prolog choicepoint
and will consume resources in the server until the call is
closed by a control message, i.e. by a cut
(see Cut), or by a (failing) retry (see Backtracking).
A call request should specify a unique identifier member, to
ensure that any cut or retry can refer to it unambiguously.