diff --git a/doc/api.rst b/doc/api.rst deleted file mode 100644 index 4d5f846f497bc771894ac93297b8390b6d0d935f..0000000000000000000000000000000000000000 --- a/doc/api.rst +++ /dev/null @@ -1,32 +0,0 @@ -sjRpc API -========= - -.. automodule:: sjrpc - -Core library ------------- - -.. automodule:: sjrpc.core - :members: - :inherited-members: - -Client side library -------------------- - -.. automodule:: sjrpc.client - :members: - :inherited-members: - -Server side library -------------------- - -.. automodule:: sjrpc.server - :members: - :inherited-members: - -Utils ------ - -.. automodule:: sjrpc.utils - :members: - :inherited-members: diff --git a/doc/api/core.rst b/doc/api/core.rst new file mode 100644 index 0000000000000000000000000000000000000000..595bc0e59c431af02a9a274ba5a00a49a4588518 --- /dev/null +++ b/doc/api/core.rst @@ -0,0 +1,7 @@ + +Core library +------------ + +.. automodule:: sjrpc.core + :members: + :inherited-members: diff --git a/doc/api/index.rst b/doc/api/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..e0d281283e1afea233f09588606acbf8a2a521fc --- /dev/null +++ b/doc/api/index.rst @@ -0,0 +1,14 @@ +sjRpc API +========= + +.. automodule:: sjrpc + +Sub-packages: + +.. toctree:: + :maxdepth: 2 + + core + server + utils + diff --git a/doc/api/server.rst b/doc/api/server.rst new file mode 100644 index 0000000000000000000000000000000000000000..b8f70decac736d8383ae2c31c65acf24f4366679 --- /dev/null +++ b/doc/api/server.rst @@ -0,0 +1,7 @@ + +Server side library +------------------- + +.. automodule:: sjrpc.server + :members: + :inherited-members: diff --git a/doc/api/utils.rst b/doc/api/utils.rst new file mode 100644 index 0000000000000000000000000000000000000000..8f4b8321704a0d685b282b837c9efeea619c93e6 --- /dev/null +++ b/doc/api/utils.rst @@ -0,0 +1,7 @@ + +Utils +----- + +.. automodule:: sjrpc.utils + :members: + :inherited-members: diff --git a/doc/conf.py b/doc/conf.py index d5919cd4380b76f2a66727daabf4afe428f532d5..a9c5760173f6ce7a274ed70795d608254edcdff6 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -22,7 +22,8 @@ sys.path.append(os.path.abspath('../')) # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig'] +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', + 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig', 'sphinx.ext.autosummary'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -92,7 +93,7 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'default' +html_theme = 'nature' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/doc/examples.rst b/doc/examples.rst new file mode 100644 index 0000000000000000000000000000000000000000..cc839cf369eb7615865a0498adef4b5906db693d --- /dev/null +++ b/doc/examples.rst @@ -0,0 +1,17 @@ +Examples +======== + +Client example +-------------- + +.. literalinclude:: examples/client.py + :language: python + :linenos: + +Server example +-------------- + +.. literalinclude:: examples/server.py + :language: python + :linenos: + diff --git a/doc/examples/client.py b/doc/examples/client.py new file mode 100644 index 0000000000000000000000000000000000000000..75a4283f7b9d4d5c1e1ef2a9d2190532f495fe3b --- /dev/null +++ b/doc/examples/client.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +''' +sjRpc client example. +''' + +from __future__ import absolute_import + +import sys +import random +import threading +import time + +from sjrpc.core import RpcConnection +from sjrpc.utils import RpcHandler + + +class MyClientHandler(RpcHandler): + + def client_random(self, min=0, max=100): + print 'Local call to client_random' + return random.randint(min, max) + + +# Get arguments from the command line: +if len(sys.argv) < 3: + print 'Usage: %s ' % sys.argv[0] + sys.exit(2) +address = sys.argv[1] +port = int(sys.argv[2]) + +# Create the rpc connection: +conn = RpcConnection.from_addr(address, port, handler=MyClientHandler()) + +# Run the connection mainloop in another thread: +threading.Thread(target=conn.run).start() +time.sleep(0.1) +print 'Random = %s' % (conn.call('proxy', 'client_random'), ) + +conn.shutdown() diff --git a/doc/examples/server.py b/doc/examples/server.py new file mode 100644 index 0000000000000000000000000000000000000000..571b98998927a0e1eebcc7205fda977454b09a40 --- /dev/null +++ b/doc/examples/server.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +''' +Server mode using Gevent. +''' + +from __future__ import absolute_import + +import sys +import random +from sjrpc.server import GreenRpcServer +from sjrpc.utils import RpcHandler, pass_connection + + +class MyHandler(RpcHandler): + + def random(self, min=0, max=100): + return random.randint(min, max) + + @pass_connection + def proxy(self, conn, method, *args, **kwargs): + ''' + Example of bidirectionnal RPC. When the peer call this method, the + server forward the call of the specified method the peer, and return + returned value. + ''' + return conn.call(method, *args, **kwargs) + +# Get arguments from the command line: +if len(sys.argv) < 3: + print 'Usage: %s ' % sys.argv[0] + sys.exit(2) +address = sys.argv[1] +port = int(sys.argv[2]) + +# Create the server instance: +rpcserver = GreenRpcServer(address, port, conn_kw=dict(handler=MyHandler())) + +# conn_kw (and conn_args) are arguments which are automatically passed on +# client RpcConnection instances. + +rpcserver.run() diff --git a/doc/fundamentals.rst b/doc/fundamentals.rst new file mode 100644 index 0000000000000000000000000000000000000000..5804ce6d420364671dfda65e88138fe1f1cc140f --- /dev/null +++ b/doc/fundamentals.rst @@ -0,0 +1,73 @@ +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 call by both side of the, + connection, so a client can connect to a server and export functions to it. + * **Use Gevent:** the server mode can use Gevent to take profits of + asynchronous io, but a threaded mode is also provided. The client mode can + be used without Gevent or Threading. + * **Multiplexed:** sjRpc can run many "protocols" on the same connection, + read more about protocols in `Multiplexing & protocols`_ section. + + +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. + +.. note:: + A Rpc protocol is automatically created and binded to label 0 when a + :class:`RpcConnection` class is instantiated. This can't be changed or + removed. + + +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`. diff --git a/doc/index.rst b/doc/index.rst index f0d3e0ee3e83e3cd4a127269f06d0a54ea82062f..b0293f92fca9428a03d59693c620bacc49449a06 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -6,7 +6,9 @@ Contents: .. toctree:: :maxdepth: 2 - api + fundamentals + examples + api/index Indices and tables ================== diff --git a/sjrpc/__init__.py b/sjrpc/__init__.py index 4b90c73631596571eedc13168f517ac88b95b442..05d206a271a09516a39038825840831eca23397d 100644 --- a/sjrpc/__init__.py +++ b/sjrpc/__init__.py @@ -5,11 +5,17 @@ This module implements a Remote Procedure Call system using socket objects as transport. The main feature of this RPC is to be bidirectionnal: both client and server can serve remote procedure for its peer. -The library is separated into four parts: +Features: - * core library contains all common classes. - * server library contains all the server side related stuff. - * utils library contains some helpers used in previous libraries. + * Multiplexed: you can run tunnels or many RPC through the same socket. + * Gevent: sjRpc is compatible with gevent and use it for server feature. + * Bidirectionnal: each peer can call remote function on other. + +The library is separated into three parts: + + * **core** package contains all common classes. + * **server** package contains all the server side related stuff. + * **utils** package contains some helpers used in previous libraries. ''' diff --git a/sjrpc/core/__init__.py b/sjrpc/core/__init__.py index e0ba8aff4999c485b85c4ba0d44059d8be03c9af..4f19d9772923aba35ede38c569c84af1e524ba2c 100644 --- a/sjrpc/core/__init__.py +++ b/sjrpc/core/__init__.py @@ -1,5 +1,19 @@ -#!/usr/bin/env python -#coding:utf8 + +''' + +The **core** package contains all classes used by both client mode and server +mode. + +This packages export following function/classes: + + * :class:`RpcConnection` which handle the connection to the peer. + * :class:`RpcCaller`, :class:`ThreadedRpcCaller`: which are used internally + by rpc protocol to execute handlers' functions. + * :class:`RpcError` which is the exception raised by rpc to wrap remote-side + exceptions. + * :class:`AsyncWatcher` which allow to make asynchronous calls. + +''' from sjrpc.core.rpcconnection import * from sjrpc.core.callers import * diff --git a/sjrpc/core/async.py b/sjrpc/core/async.py index 196b8e274a9e7efb80500192abf705ba1c7ae86f..d838e1547bfd02e8c776dbb9ffe45e98b41d9bdd 100644 --- a/sjrpc/core/async.py +++ b/sjrpc/core/async.py @@ -7,8 +7,7 @@ from Queue import Queue, Empty class AsyncWatcher(object): ''' - Asynchronous call watcher -- Handle reception of asynchrone-request - responses. + Asynchronous call watcher -- Handle asynchronous calls and responses. Usage example: >>> watcher = AsyncWatcher() @@ -57,16 +56,16 @@ class AsyncWatcher(object): def wait(self, timeout=None, max_wait=None): ''' - Wait messages registered on this :class:`AsyncWatcher` instance and - return them. + Wait responses for calls registered on this :class:`AsyncWatcher` + instance and return them. :param timeout: timeout value or None to disable timeout (default: None) - :param max_wait: maximum waited messages + :param max_wait: maximum waited responses :return: received messages in a list .. note:: You can repeat call to this method on a single - :class:`AsyncWatcher`, for example, if you want to process the + :class:`AsyncWatcher`. For example, if you want to process the first arrived message, then wait others for a minute, you can do: >>> msgs = watcher.wait(max_wait=1) diff --git a/sjrpc/core/rpcconnection.py b/sjrpc/core/rpcconnection.py index 470c2e98e12b87ddebf28ee3435ecf33a6ac25de..14127367e2544d606b814bd9afeb125004591154 100644 --- a/sjrpc/core/rpcconnection.py +++ b/sjrpc/core/rpcconnection.py @@ -20,7 +20,7 @@ class RpcConnection(object): This class manage a single peer connection. :param sock: the socket object of this newly created :class:`RpcConnection` - :param *args, **kwargs: arguments to pass to the default rpc protocol + :param \*args,\*\*kwargs: arguments to pass to the default rpc protocol automatically registered on label 0. ''' @@ -67,7 +67,7 @@ class RpcConnection(object): :param addr: the target ip address :param port: the target port :param conn_timeout: the connection operation timeout - :param *args, **kwargs: extra argument to pass to the constructor (see + :param \*args,\*\*kwargs: extra argument to pass to the constructor (see constructor doctring) ''' sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -83,7 +83,7 @@ class RpcConnection(object): Construct :class:`RpcConnection` instance like :meth:`from_addr`, but enable ssl on socket. - :param cert: ssl certificate or None for ssl without certificat + :param cert: ssl client certificate or None for ssl without certificat ''' sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(conn_timeout) diff --git a/sjrpc/examples/simple_client.py b/sjrpc/examples/simple_client.py deleted file mode 100644 index 97169fa88c1454eb62771b115b2dc2250b039f51..0000000000000000000000000000000000000000 --- a/sjrpc/examples/simple_client.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python -#coding:utf8 - -from rpc.client import SimpleRpcClient -from rpc.utils import ConnectionProxy -import socket -import threading - -# Initialization of the client socket: -sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -sock.connect(('127.0.0.1', 1234)) - -# Create the client objet binded on the socket we create above: -client = SimpleRpcClient(sock) - -# Launch the event loop in separated thread: -threading.Thread(target=client.run).start() - -# Create the proxy object that allow us to call easily methods on the server: -proxy = ConnectionProxy(client) - -print '42 + 42 = ', proxy.add(42, 42) - -# You can start this script with -i argument of the python interpreter to call -# methods with proxy interactively. diff --git a/sjrpc/examples/simple_server.py b/sjrpc/examples/simple_server.py deleted file mode 100644 index 835b66f1f02047e165e3fd230e6a34bfa03177ac..0000000000000000000000000000000000000000 --- a/sjrpc/examples/simple_server.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python -#coding:utf8 - -from rpc.server import SimpleRpcServer -import socket - -def add(op1, op2): - # This will be printed on server side: - print 'add %s + %s' % (op1, op2) - # This will be returned to the client: - return op1 + op2 - -handler = { - 'add': add -} - -# Initialization of the server socket -sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -sock.bind(('0.0.0.0', 1234)) -sock.listen(5) - -# Create the server objet binded on the socket we create above: -server = SimpleRpcServer(sock, default_handler=handler) - -# Launch the server's event loop with proper exit when exception is raised: -try: - server.run() -except Exception as err: - print err - server.shutdown() -