Next: Process-based JSONRPC connections, Up: JSONRPC communication [Contents][Index]
Quoting from the spec, JSONRPC "is transport agnostic in that the concepts can be used within the same process, over sockets, over http, or in many various message passing environments."
To model this agnosticism, the jsonrpc
library uses objects of
a jsonrpc-connection
class, which represent a connection to a
remote JSON endpoint (for details on Emacs’s object system,
see EIEIO in EIEIO). In modern object-oriented parlance,
this class is “abstract”, i.e. the actual class of a useful
connection object is always a subclass of jsonrpc-connection
.
Nevertheless, we can define two distinct APIs around the
jsonrpc-connection
class:
In this scenario, a new aspiring JSONRPC-based application selects a
concrete subclass of jsonrpc-connection
that provides the
transport for the JSONRPC messages to be exchanged between endpoints.
The application creates objects of that subclass using
make-instance
. To initiate a contact to a remote endpoint, the
application passes this object to the functions such as
jsonrpc-notify
, jsonrpc-request
, or
jsonrpc-async-request
.
For handling remotely initiated contacts, which generally come in
asynchronously, the make-instance
instantiation should
initialize it the :request-dispatcher
and
:notification-dispatcher
EIEIO keyword arguments. These are
both functions of 3 arguments: the connection object; a symbol naming
the JSONRPC method invoked remotely; and a JSONRPC params
object.
The function passed as :request-dispatcher
is responsible for
handling the remote endpoint’s requests, which expect a reply from the
local endpoint (in this case, the application you’re building).
Inside that function, you may either return locally (a regular return)
or non-locally (throw an error). Both exits from the request
dispatcher cause a reply to the remote endpoint’s request to be sent
through the transport.
A regular return determines a success response, and the return value
must be a Lisp object that can be serialized as JSON (see Parsing and generating JSON values). The result is forwarded to the server as the JSONRPC
result
object. A non-local return, achieved by calling the
function jsonrpc-error
, causes an error response to be sent to
the server. The details of the accompanying JSONRPC error
object are filled out with whatever was passed to
jsonrpc-error
. A non-local return triggered by an unexpected
error of any other type also causes an error response to be sent
(unless you have set debug-on-error
, in which case this calls
the Lisp debugger, see Entering the Debugger on an Error).
It’s possible to use the jsonrpc
library to build applications
based on transport protocols that can be described as
“quasi-JSONRPC”. These are similar, but not quite identical to
JSONRPC, such as the DAP (Debug
Adapter Protocol). These protocols also define request, response and
notification messages but the format is not quite the same as JSONRPC.
The generic functions jsonrpc-convert-to-endpoint
and
jsonrpc-convert-from-endpoint
can be customized for converting
between the internal representation of JSONRPC and whatever the
endpoint accepts (see Generic Functions).
In this scenario, jsonrpc-connection
is sub-classed to implement
a different underlying transport strategy (for details on how to
subclass, see (eieio)Inheritance.). Users of the
application-building interface can then instantiate objects of this
concrete class (using the make-instance
function) and connect
to JSONRPC endpoints using that strategy. See Process-based JSONRPC connections for a built-in transport implementation.
This API has mandatory and optional parts.
To allow its users to initiate JSONRPC contacts (notifications or
requests) or reply to endpoint requests, the new transport
implementation must equip the jsonrpc-connection-send
generic
function with a specialization for the new subclass
(see Generic Functions). This generic function is called
automatically by primitives such as jsonrpc-request
and
jsonrpc-notify
. The specialization should ensure that the
message described in the argument list is sent through whatever
underlying communication mechanism (a.k.a. “wire”) is used by the
new transport to talk to endpoints. This “wire” may be a network
socket, a serial interface, an HTTP connection, etc.
Likewise, for handling the three types of remote contacts (requests,
notifications, and responses to local requests), the transport
implementation must arrange for the function
jsonrpc-connection-receive
to be called from Elisp after
noticing some data on the “wire” that can be used to craft a JSONRPC
(or quasi-JSONRPC) message.
Finally, and optionally, the jsonrpc-connection
subclass should
add specializations to the jsonrpc-shutdown
and
jsonrpc-running-p
generic functions if these concepts apply to
the transport. The specialization of jsonrpc-shutdown
should
ensure the release of any system resources (e.g. processes, timers,
etc.) used to listen for messages on the wire. The specialization of
jsonrpc-running-p
should tell if these resources are still
active or have already been released (via jsonrpc-shutdown
or
otherwise).
Next: Process-based JSONRPC connections, Up: JSONRPC communication [Contents][Index]