Skip to content
fundamentals.rst 5.2 KiB
Newer Older
Fundamentals
============

sjRpc is a RPC (Remote Procedure Call) library written with Python. It was
originally created for the needs of CloudControl project but can be reused
in other projects.

Features
--------

 * **Bidirectional:** remote function can be called by both side of the
   connection, so a client can connect to a server and export functions to it.
 * **Fully event based**: sjRpc takes profits of asynchronous io, allowing a
   server to handle thousands of client connection with a reasonable memory
   usage.
 * **Multiplexed:** sjRpc can run many "protocols" on the same connection,
   read more about protocols in `Multiplexing & protocols`_ section.
 * **Fallback mode:** for compatibility with olders sjRpc.
Antoine Millet's avatar
Antoine Millet committed
Basic usage
-----------

Server side, create the handler
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The handler is a simple object with a dict interface (getitem) responsible of
the association between the remote-function name and the locally executed
callable. The :class:`sjrpc.utils.Handler` class help you to define
Antoine Millet's avatar
Antoine Millet committed
an handle with a simple class extending this one::

  >>> class MyHandler(RpcHandler):

  ... def random(self, min=0, max=100):
  ...     return random.randint(min, max)
  ...
  >>> handler = MyHandler()

But if you want to use a standard dictionnary, this is exactly the same::

  >>> handler = {'random': lambda min, max: random.randint(min, max)}

Server side, create the :class:`~sjrpc.core.RpcServer` instance
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The last thing to do on the server side is to launch the server itself::

  >>> from sjrpc.server import RpcServer
  >>> serv = RpcServer.from_addr('127.0.0.1', 1337, conn_kw=dict(handler=handler))
Antoine Millet's avatar
Antoine Millet committed
  >>> serv.run()

.. note::

   `conn_args` and `conn_kw` are the arguments which are automatically passed
   to each client :class:`~sjrpc.core.RpcConnection` instanciation. In this
   example, we just pass a default handler.

Client side, just connect !
^^^^^^^^^^^^^^^^^^^^^^^^^^^

For a basic client usage, the only thing you have to do is to create the
:class:`~sjrpc.core.RpcConnection` instance to the server and start the
:class:`~sjrpc.core.RpcConnection` main-loop in another thread to keep the
hands on your term::
Antoine Millet's avatar
Antoine Millet committed

  >>> conn = RpcConnection.from_addr('127.0.0.1', 1337)
  >>> threading.Thread(target=conn.run).start()
Antoine Millet's avatar
Antoine Millet committed
  >>> print conn.call('random')
  42

You can also use a proxy to simplify remote calls::

  >>> from sjrpc.utils import ConnectionProxy
  >>> proxy = ConnectionProxy(conn)
  >>> print proxy.random(min=42, max=1000)
  587

Proxy will also restore built-in exceptions embedded in
:class:`~sjrpc.core.RpcError`.

Multiplexing & protocols
------------------------

Protocol of sjRpc use channels to multiplexe many protocols on the
same connection. Each channel is binded to a protocol handler on each side, and
each channel have a label which identify it on the wire. Actually, the sjRpc
protocol look like this::

  +------------+------------------------+---------------------------------+
  | Label (2)  | Payload size (4)       | Payload (variable)              |
  +------------+------------------------+---------------------------------+

For the moment, two types of protocols are implemented:

 * **Rpc:** protocol which allow to make remote function call easily.
 * **Tunnel:** protocol which allow to tunnel a socket through the sjRpc
   connection.

To register new protocols, you can use the :meth:`register_protocol` on
:class:`RpcConnection` instances, like this::
  >>> from sjrpc.core.protocols import RpcProtocol, TunnelProtocol
  >>> my_tunnel = myconn.register_protocol(1, TunnelProtocol)

  do the same on other side, then

  >>> mytunnel.send('ehlo !!')
  >>> anwser = mytunnel.recv()

You can also use shortcuts::

  >>> my_tunnel = myconn.create_tunnel(label=12)

  or

  >>> my_tunnel = myconn.create_tunnel() # Bind to the first free label

The same shortcut is available for rpc with :meth:`create_rpc`.

Default rpc, aka Rpc0
---------------------

Rpc0 is an RpcProtocol binded by default with the "0" label. You can't
unregister this protocol, and you can't disable this feature. Rpc0 is used
internally by sjRpc to share special messages, and for compatibility with
olders sjRpc (see `Fallback mode`_ below).

However, you can use this rpc like any other, with your own rpc handler.


Fallback mode
-------------

The fallback mode makes the *sjrpc >= 14* compatible with olders version where
channels and protocols doesn't exists. Old and new protocols are very similar,
the new one just add a label field on each frame which allow multiplexing, but
the rpc protocol itself has not changed, and is always using json messaging.

The fallback mode is enabled by default when a connection is established (you
can disable this behavior with the `disable_fallback` parameter of the
:class:`RpcConnection` class constructor) and is automatically disabled when
a special rpc message "capabilities" is received.

.. note::
   When the fallback mode is enabled, you can't use another protocols than the
   default rpc. All calls to :meth:`RpcConnection.register_protocol`,
   :meth:`RpcConnection.unregister_protocol`, :meth:`RpcConnection.create_rpc`
   and :meth:`RpcConnection.create_tunnel`, will fail with a
   :exc:`FallbackModeEnabledError`.