contrib: import tinyrpc library
https://pypi.python.org/pypi/tinyrpc/0.5 https://github.com/mbr/tinyrpc Signed-off-by: Yoshihiro Kaneko <ykaneko0929@gmail.com> Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
This commit is contained in:
parent
dbb143f972
commit
78a9a20270
6
ryu/contrib/tinyrpc/__init__.py
Normal file
6
ryu/contrib/tinyrpc/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from .protocols import *
|
||||||
|
from .exc import *
|
||||||
|
from .client import *
|
91
ryu/contrib/tinyrpc/client.py
Normal file
91
ryu/contrib/tinyrpc/client.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from .exc import RPCError
|
||||||
|
|
||||||
|
|
||||||
|
class RPCClient(object):
|
||||||
|
"""Client for making RPC calls to connected servers.
|
||||||
|
|
||||||
|
:param protocol: An :py:class:`~tinyrpc.RPCProtocol` instance.
|
||||||
|
:param transport: A :py:class:`~tinyrpc.transports.ClientTransport`
|
||||||
|
instance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, protocol, transport):
|
||||||
|
self.protocol = protocol
|
||||||
|
self.transport = transport
|
||||||
|
|
||||||
|
def _send_and_handle_reply(self, req):
|
||||||
|
# sends and waits for reply
|
||||||
|
reply = self.transport.send_message(req.serialize())
|
||||||
|
|
||||||
|
response = self.protocol.parse_reply(reply)
|
||||||
|
|
||||||
|
if hasattr(response, 'error'):
|
||||||
|
raise RPCError('Error calling remote procedure: %s' %\
|
||||||
|
response.error)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def call(self, method, args, kwargs, one_way=False):
|
||||||
|
"""Calls the requested method and returns the result.
|
||||||
|
|
||||||
|
If an error occured, an :py:class:`~tinyrpc.exc.RPCError` instance
|
||||||
|
is raised.
|
||||||
|
|
||||||
|
:param method: Name of the method to call.
|
||||||
|
:param args: Arguments to pass to the method.
|
||||||
|
:param kwargs: Keyword arguments to pass to the method.
|
||||||
|
:param one_way: Whether or not a reply is desired.
|
||||||
|
"""
|
||||||
|
req = self.protocol.create_request(method, args, kwargs, one_way)
|
||||||
|
|
||||||
|
return self._send_and_handle_reply(req).result
|
||||||
|
|
||||||
|
def get_proxy(self, prefix='', one_way=False):
|
||||||
|
"""Convenience method for creating a proxy.
|
||||||
|
|
||||||
|
:param prefix: Passed on to :py:class:`~tinyrpc.client.RPCProxy`.
|
||||||
|
:param one_way: Passed on to :py:class:`~tinyrpc.client.RPCProxy`.
|
||||||
|
:return: :py:class:`~tinyrpc.client.RPCProxy` instance."""
|
||||||
|
return RPCProxy(self, prefix, one_way)
|
||||||
|
|
||||||
|
def batch_call(self, calls):
|
||||||
|
"""Experimental, use at your own peril."""
|
||||||
|
req = self.protocol.create_batch_request()
|
||||||
|
|
||||||
|
for call_args in calls:
|
||||||
|
req.append(self.protocol.create_request(*call_args))
|
||||||
|
|
||||||
|
return self._send_and_handle_reply(req)
|
||||||
|
|
||||||
|
|
||||||
|
class RPCProxy(object):
|
||||||
|
"""Create a new remote proxy object.
|
||||||
|
|
||||||
|
Proxies allow calling of methods through a simpler interface. See the
|
||||||
|
documentation for an example.
|
||||||
|
|
||||||
|
:param client: An :py:class:`~tinyrpc.client.RPCClient` instance.
|
||||||
|
:param prefix: Prefix to prepend to every method name.
|
||||||
|
:param one_way: Passed to every call of
|
||||||
|
:py:func:`~tinyrpc.client.call`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, client, prefix='', one_way=False):
|
||||||
|
self.client = client
|
||||||
|
self.prefix = prefix
|
||||||
|
self.one_way = one_way
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
"""Returns a proxy function that, when called, will call a function
|
||||||
|
name ``name`` on the client associated with the proxy.
|
||||||
|
"""
|
||||||
|
proxy_func = lambda *args, **kwargs: self.client.call(
|
||||||
|
self.prefix + name,
|
||||||
|
args,
|
||||||
|
kwargs,
|
||||||
|
one_way=self.one_way
|
||||||
|
)
|
||||||
|
return proxy_func
|
201
ryu/contrib/tinyrpc/dispatch/__init__.py
Normal file
201
ryu/contrib/tinyrpc/dispatch/__init__.py
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
from ..exc import *
|
||||||
|
|
||||||
|
|
||||||
|
def public(name=None):
|
||||||
|
"""Set RPC name on function.
|
||||||
|
|
||||||
|
This function decorator will set the ``_rpc_public_name`` attribute on a
|
||||||
|
function, causing it to be picked up if an instance of its parent class is
|
||||||
|
registered using
|
||||||
|
:py:func:`~tinyrpc.dispatch.RPCDispatcher.register_instance`.
|
||||||
|
|
||||||
|
``@public`` is a shortcut for ``@public()``.
|
||||||
|
|
||||||
|
:param name: The name to register the function with.
|
||||||
|
"""
|
||||||
|
# called directly with function
|
||||||
|
if callable(name):
|
||||||
|
f = name
|
||||||
|
f._rpc_public_name = f.__name__
|
||||||
|
return f
|
||||||
|
|
||||||
|
def _(f):
|
||||||
|
f._rpc_public_name = name or f.__name__
|
||||||
|
return f
|
||||||
|
|
||||||
|
return _
|
||||||
|
|
||||||
|
|
||||||
|
class RPCDispatcher(object):
|
||||||
|
"""Stores name-to-method mappings."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.method_map = {}
|
||||||
|
self.subdispatchers = {}
|
||||||
|
|
||||||
|
def add_subdispatch(self, dispatcher, prefix=''):
|
||||||
|
"""Adds a subdispatcher, possibly in its own namespace.
|
||||||
|
|
||||||
|
:param dispatcher: The dispatcher to add as a subdispatcher.
|
||||||
|
:param prefix: A prefix. All of the new subdispatchers methods will be
|
||||||
|
available as prefix + their original name.
|
||||||
|
"""
|
||||||
|
self.subdispatchers.setdefault(prefix, []).append(dispatcher)
|
||||||
|
|
||||||
|
def add_method(self, f, name=None):
|
||||||
|
"""Add a method to the dispatcher.
|
||||||
|
|
||||||
|
:param f: Callable to be added.
|
||||||
|
:param name: Name to register it with. If ``None``, ``f.__name__`` will
|
||||||
|
be used.
|
||||||
|
"""
|
||||||
|
assert callable(f), "method argument must be callable"
|
||||||
|
# catches a few programming errors that are
|
||||||
|
# commonly silently swallowed otherwise
|
||||||
|
if not name:
|
||||||
|
name = f.__name__
|
||||||
|
|
||||||
|
if name in self.method_map:
|
||||||
|
raise RPCError('Name %s already registered')
|
||||||
|
|
||||||
|
self.method_map[name] = f
|
||||||
|
|
||||||
|
def dispatch(self, request):
|
||||||
|
"""Fully handle request.
|
||||||
|
|
||||||
|
The dispatch method determines which method to call, calls it and
|
||||||
|
returns a response containing a result.
|
||||||
|
|
||||||
|
No exceptions will be thrown, rather, every exception will be turned
|
||||||
|
into a response using :py:func:`~tinyrpc.RPCRequest.error_respond`.
|
||||||
|
|
||||||
|
If a method isn't found, a :py:exc:`~tinyrpc.exc.MethodNotFoundError`
|
||||||
|
response will be returned. If any error occurs outside of the requested
|
||||||
|
method, a :py:exc:`~tinyrpc.exc.ServerError` without any error
|
||||||
|
information will be returend.
|
||||||
|
|
||||||
|
If the method is found and called but throws an exception, the
|
||||||
|
exception thrown is used as a response instead. This is the only case
|
||||||
|
in which information from the exception is possibly propagated back to
|
||||||
|
the client, as the exception is part of the requested method.
|
||||||
|
|
||||||
|
:py:class:`~tinyrpc.RPCBatchRequest` instances are handled by handling
|
||||||
|
all its children in order and collecting the results, then returning an
|
||||||
|
:py:class:`~tinyrpc.RPCBatchResponse` with the results.
|
||||||
|
|
||||||
|
:param request: An :py:func:`~tinyrpc.RPCRequest`.
|
||||||
|
:return: An :py:func:`~tinyrpc.RPCResponse`.
|
||||||
|
"""
|
||||||
|
if hasattr(request, 'create_batch_response'):
|
||||||
|
results = [self._dispatch(req) for req in request]
|
||||||
|
|
||||||
|
response = request.create_batch_response()
|
||||||
|
if response != None:
|
||||||
|
response.extend(results)
|
||||||
|
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
return self._dispatch(request)
|
||||||
|
|
||||||
|
def _dispatch(self, request):
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
method = self.get_method(request.method)
|
||||||
|
except KeyError as e:
|
||||||
|
return request.error_respond(MethodNotFoundError(e))
|
||||||
|
|
||||||
|
# we found the method
|
||||||
|
try:
|
||||||
|
result = method(*request.args, **request.kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
# an error occured within the method, return it
|
||||||
|
return request.error_respond(e)
|
||||||
|
|
||||||
|
# respond with result
|
||||||
|
return request.respond(result)
|
||||||
|
except Exception as e:
|
||||||
|
# unexpected error, do not let client know what happened
|
||||||
|
return request.error_respond(ServerError())
|
||||||
|
|
||||||
|
def get_method(self, name):
|
||||||
|
"""Retrieve a previously registered method.
|
||||||
|
|
||||||
|
Checks if a method matching ``name`` has been registered.
|
||||||
|
|
||||||
|
If :py:func:`get_method` cannot find a method, every subdispatcher
|
||||||
|
with a prefix matching the method name is checked as well.
|
||||||
|
|
||||||
|
If a method isn't found, a :py:class:`KeyError` is thrown.
|
||||||
|
|
||||||
|
:param name: Callable to find.
|
||||||
|
:param return: The callable.
|
||||||
|
"""
|
||||||
|
if name in self.method_map:
|
||||||
|
return self.method_map[name]
|
||||||
|
|
||||||
|
for prefix, subdispatchers in self.subdispatchers.iteritems():
|
||||||
|
if name.startswith(prefix):
|
||||||
|
for sd in subdispatchers:
|
||||||
|
try:
|
||||||
|
return sd.get_method(name[len(prefix):])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
raise KeyError(name)
|
||||||
|
|
||||||
|
def public(self, name=None):
|
||||||
|
"""Convenient decorator.
|
||||||
|
|
||||||
|
Allows easy registering of functions to this dispatcher. Example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
dispatch = RPCDispatcher()
|
||||||
|
|
||||||
|
@dispatch.public
|
||||||
|
def foo(bar):
|
||||||
|
# ...
|
||||||
|
|
||||||
|
class Baz(object):
|
||||||
|
def not_exposed(self):
|
||||||
|
# ...
|
||||||
|
|
||||||
|
@dispatch.public(name='do_something')
|
||||||
|
def visible_method(arg1)
|
||||||
|
# ...
|
||||||
|
|
||||||
|
:param name: Name to register callable with
|
||||||
|
"""
|
||||||
|
if callable(name):
|
||||||
|
self.add_method(name)
|
||||||
|
return name
|
||||||
|
|
||||||
|
def _(f):
|
||||||
|
self.add_method(f, name=name)
|
||||||
|
return f
|
||||||
|
|
||||||
|
return _
|
||||||
|
|
||||||
|
def register_instance(self, obj, prefix=''):
|
||||||
|
"""Create new subdispatcher and register all public object methods on
|
||||||
|
it.
|
||||||
|
|
||||||
|
To be used in conjunction with the :py:func:`tinyrpc.dispatch.public`
|
||||||
|
decorator (*not* :py:func:`tinyrpc.dispatch.RPCDispatcher.public`).
|
||||||
|
|
||||||
|
:param obj: The object whose public methods should be made available.
|
||||||
|
:param prefix: A prefix for the new subdispatcher.
|
||||||
|
"""
|
||||||
|
dispatch = self.__class__()
|
||||||
|
for name, f in inspect.getmembers(
|
||||||
|
obj, lambda f: callable(f) and hasattr(f, '_rpc_public_name')
|
||||||
|
):
|
||||||
|
dispatch.add_method(f, f._rpc_public_name)
|
||||||
|
|
||||||
|
# add to dispatchers
|
||||||
|
self.add_subdispatch(dispatch, prefix)
|
40
ryu/contrib/tinyrpc/exc.py
Normal file
40
ryu/contrib/tinyrpc/exc.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
class RPCError(Exception):
|
||||||
|
"""Base class for all excetions thrown by :py:mod:`tinyrpc`."""
|
||||||
|
|
||||||
|
|
||||||
|
class BadRequestError(RPCError):
|
||||||
|
"""Base class for all errors that caused the processing of a request to
|
||||||
|
abort before a request object could be instantiated."""
|
||||||
|
|
||||||
|
def error_respond(self):
|
||||||
|
"""Create :py:class:`~tinyrpc.RPCErrorResponse` to respond the error.
|
||||||
|
|
||||||
|
:return: A error responce instance or ``None``, if the protocol decides
|
||||||
|
to drop the error silently."""
|
||||||
|
raise RuntimeError('Not implemented')
|
||||||
|
|
||||||
|
|
||||||
|
class BadReplyError(RPCError):
|
||||||
|
"""Base class for all errors that caused processing of a reply to abort
|
||||||
|
before it could be turned in a response object."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidRequestError(BadRequestError):
|
||||||
|
"""A request made was malformed (i.e. violated the specification) and could
|
||||||
|
not be parsed."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidReplyError(BadReplyError):
|
||||||
|
"""A reply received was malformed (i.e. violated the specification) and
|
||||||
|
could not be parsed into a response."""
|
||||||
|
|
||||||
|
|
||||||
|
class MethodNotFoundError(RPCError):
|
||||||
|
"""The desired method was not found."""
|
||||||
|
|
||||||
|
|
||||||
|
class ServerError(RPCError):
|
||||||
|
"""An internal error in the RPC system occured."""
|
173
ryu/contrib/tinyrpc/protocols/__init__.py
Normal file
173
ryu/contrib/tinyrpc/protocols/__init__.py
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from ..exc import *
|
||||||
|
|
||||||
|
class RPCRequest(object):
|
||||||
|
unique_id = None
|
||||||
|
"""A unique ID to remember the request by. Protocol specific, may or
|
||||||
|
may not be set. This value should only be set by
|
||||||
|
:py:func:`~tinyrpc.RPCProtocol.create_request`.
|
||||||
|
|
||||||
|
The ID allows client to receive responses out-of-order and still allocate
|
||||||
|
them to the correct request.
|
||||||
|
|
||||||
|
Only supported if the parent protocol has
|
||||||
|
:py:attr:`~tinyrpc.RPCProtocol.supports_out_of_order` set to ``True``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
method = None
|
||||||
|
"""The name of the method to be called."""
|
||||||
|
|
||||||
|
args = []
|
||||||
|
"""The positional arguments of the method call."""
|
||||||
|
|
||||||
|
kwargs = {}
|
||||||
|
"""The keyword arguments of the method call."""
|
||||||
|
|
||||||
|
def error_respond(self, error):
|
||||||
|
"""Creates an error response.
|
||||||
|
|
||||||
|
Create a response indicating that the request was parsed correctly,
|
||||||
|
but an error has occured trying to fulfill it.
|
||||||
|
|
||||||
|
:param error: An exception or a string describing the error.
|
||||||
|
|
||||||
|
:return: A response or ``None`` to indicate that no error should be sent
|
||||||
|
out.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def respond(self, result):
|
||||||
|
"""Create a response.
|
||||||
|
|
||||||
|
Call this to return the result of a successful method invocation.
|
||||||
|
|
||||||
|
This creates and returns an instance of a protocol-specific subclass of
|
||||||
|
:py:class:`~tinyrpc.RPCResponse`.
|
||||||
|
|
||||||
|
:param result: Passed on to new response instance.
|
||||||
|
|
||||||
|
:return: A response or ``None`` to indicate this request does not expect a
|
||||||
|
response.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
"""Returns a serialization of the request.
|
||||||
|
|
||||||
|
:return: A string to be passed on to a transport.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class RPCBatchRequest(list):
|
||||||
|
"""Multiple requests batched together.
|
||||||
|
|
||||||
|
A batch request is a subclass of :py:class:`list`. Protocols that support
|
||||||
|
multiple requests in a single message use this to group them together.
|
||||||
|
|
||||||
|
Handling a batch requests is done in any order, responses must be gathered
|
||||||
|
in a batch response and be in the same order as their respective requests.
|
||||||
|
|
||||||
|
Any item of a batch request is either a request or a subclass of
|
||||||
|
:py:class:`~tinyrpc.BadRequestError`, which indicates that there has been
|
||||||
|
an error in parsing the request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def create_batch_response(self):
|
||||||
|
"""Creates a response suitable for responding to this request.
|
||||||
|
|
||||||
|
:return: An :py:class:`~tinyrpc.RPCBatchResponse` or ``None``, if no
|
||||||
|
response is expected."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class RPCResponse(object):
|
||||||
|
"""RPC call response class.
|
||||||
|
|
||||||
|
Base class for all deriving responses.
|
||||||
|
|
||||||
|
Has an attribute ``result`` containing the result of the RPC call, unless
|
||||||
|
an error occured, in which case an attribute ``error`` will contain the
|
||||||
|
error message."""
|
||||||
|
|
||||||
|
unique_id = None
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
"""Returns a serialization of the response.
|
||||||
|
|
||||||
|
:return: A reply to be passed on to a transport.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class RPCErrorResponse(RPCResponse):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RPCBatchResponse(list):
|
||||||
|
"""Multiple response from a batch request. See
|
||||||
|
:py:class:`~tinyrpc.RPCBatchRequest` on how to handle.
|
||||||
|
|
||||||
|
Items in a batch response need to be
|
||||||
|
:py:class:`~tinyrpc.RPCResponse` instances or None, meaning no reply should
|
||||||
|
generated for the request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
"""Returns a serialization of the batch response."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class RPCProtocol(object):
|
||||||
|
"""Base class for all protocol implementations."""
|
||||||
|
|
||||||
|
supports_out_of_order = False
|
||||||
|
"""If true, this protocol can receive responses out of order correctly.
|
||||||
|
|
||||||
|
Note that this usually depends on the generation of unique_ids, the
|
||||||
|
generation of these may or may not be thread safe, depending on the
|
||||||
|
protocol. Ideally, only once instance of RPCProtocol should be used per
|
||||||
|
client."""
|
||||||
|
|
||||||
|
def create_request(self, method, args=None, kwargs=None, one_way=False):
|
||||||
|
"""Creates a new RPCRequest object.
|
||||||
|
|
||||||
|
It is up to the implementing protocol whether or not ``args``,
|
||||||
|
``kwargs``, one of these, both at once or none of them are supported.
|
||||||
|
|
||||||
|
:param method: The method name to invoke.
|
||||||
|
:param args: The positional arguments to call the method with.
|
||||||
|
:param kwargs: The keyword arguments to call the method with.
|
||||||
|
:param one_way: The request is an update, i.e. it does not expect a
|
||||||
|
reply.
|
||||||
|
:return: A new :py:class:`~tinyrpc.RPCRequest` instance.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def parse_request(self, data):
|
||||||
|
"""Parses a request given as a string and returns an
|
||||||
|
:py:class:`RPCRequest` instance.
|
||||||
|
|
||||||
|
:return: An instanced request.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def parse_reply(self, data):
|
||||||
|
"""Parses a reply and returns an :py:class:`RPCResponse` instance.
|
||||||
|
|
||||||
|
:return: An instanced response.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class RPCBatchProtocol(RPCProtocol):
|
||||||
|
def create_batch_request(self, requests=None):
|
||||||
|
"""Create a new :py:class:`tinyrpc.RPCBatchRequest` object.
|
||||||
|
|
||||||
|
:param requests: A list of requests.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
291
ryu/contrib/tinyrpc/protocols/jsonrpc.py
Normal file
291
ryu/contrib/tinyrpc/protocols/jsonrpc.py
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from .. import RPCBatchProtocol, RPCRequest, RPCResponse, RPCErrorResponse,\
|
||||||
|
InvalidRequestError, MethodNotFoundError, ServerError,\
|
||||||
|
InvalidReplyError, RPCError, RPCBatchRequest, RPCBatchResponse
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
class FixedErrorMessageMixin(object):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
if not args:
|
||||||
|
args = [self.message]
|
||||||
|
super(FixedErrorMessageMixin, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def error_respond(self):
|
||||||
|
response = JSONRPCErrorResponse()
|
||||||
|
|
||||||
|
response.error = self.message
|
||||||
|
response.unique_id = None
|
||||||
|
response._jsonrpc_error_code = self.jsonrpc_error_code
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class JSONRPCParseError(FixedErrorMessageMixin, InvalidRequestError):
|
||||||
|
jsonrpc_error_code = -32700
|
||||||
|
message = 'Parse error'
|
||||||
|
|
||||||
|
|
||||||
|
class JSONRPCInvalidRequestError(FixedErrorMessageMixin, InvalidRequestError):
|
||||||
|
jsonrpc_error_code = -32600
|
||||||
|
message = 'Invalid Request'
|
||||||
|
|
||||||
|
|
||||||
|
class JSONRPCMethodNotFoundError(FixedErrorMessageMixin, MethodNotFoundError):
|
||||||
|
jsonrpc_error_code = -32601
|
||||||
|
message = 'Method not found'
|
||||||
|
|
||||||
|
|
||||||
|
class JSONRPCInvalidParamsError(FixedErrorMessageMixin, InvalidRequestError):
|
||||||
|
jsonrpc_error_code = -32602
|
||||||
|
message = 'Invalid params'
|
||||||
|
|
||||||
|
|
||||||
|
class JSONRPCInternalError(FixedErrorMessageMixin, InvalidRequestError):
|
||||||
|
jsonrpc_error_code = -32603
|
||||||
|
message = 'Internal error'
|
||||||
|
|
||||||
|
|
||||||
|
class JSONRPCServerError(FixedErrorMessageMixin, InvalidRequestError):
|
||||||
|
jsonrpc_error_code = -32000
|
||||||
|
message = ''
|
||||||
|
|
||||||
|
|
||||||
|
class JSONRPCSuccessResponse(RPCResponse):
|
||||||
|
def _to_dict(self):
|
||||||
|
return {
|
||||||
|
'jsonrpc': JSONRPCProtocol.JSON_RPC_VERSION,
|
||||||
|
'id': self.unique_id,
|
||||||
|
'result': self.result,
|
||||||
|
}
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
return json.dumps(self._to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
class JSONRPCErrorResponse(RPCErrorResponse):
|
||||||
|
def _to_dict(self):
|
||||||
|
return {
|
||||||
|
'jsonrpc': JSONRPCProtocol.JSON_RPC_VERSION,
|
||||||
|
'id': self.unique_id,
|
||||||
|
'error': {
|
||||||
|
'message': str(self.error),
|
||||||
|
'code': self._jsonrpc_error_code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
return json.dumps(self._to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
def _get_code_and_message(error):
|
||||||
|
assert isinstance(error, (Exception, basestring))
|
||||||
|
if isinstance(error, Exception):
|
||||||
|
if hasattr(error, 'jsonrpc_error_code'):
|
||||||
|
code = error.jsonrpc_error_code
|
||||||
|
msg = str(error)
|
||||||
|
elif isinstance(error, InvalidRequestError):
|
||||||
|
code = JSONRPCInvalidRequestError.jsonrpc_error_code
|
||||||
|
msg = JSONRPCInvalidRequestError.message
|
||||||
|
elif isinstance(error, MethodNotFoundError):
|
||||||
|
code = JSONRPCMethodNotFoundError.jsonrpc_error_code
|
||||||
|
msg = JSONRPCMethodNotFoundError.message
|
||||||
|
else:
|
||||||
|
# allow exception message to propagate
|
||||||
|
code = JSONRPCServerError.jsonrpc_error_code
|
||||||
|
msg = str(error)
|
||||||
|
else:
|
||||||
|
code = -32000
|
||||||
|
msg = error
|
||||||
|
|
||||||
|
return code, msg
|
||||||
|
|
||||||
|
|
||||||
|
class JSONRPCRequest(RPCRequest):
|
||||||
|
def error_respond(self, error):
|
||||||
|
if not self.unique_id:
|
||||||
|
return None
|
||||||
|
|
||||||
|
response = JSONRPCErrorResponse()
|
||||||
|
|
||||||
|
code, msg = _get_code_and_message(error)
|
||||||
|
|
||||||
|
response.error = msg
|
||||||
|
response.unique_id = self.unique_id
|
||||||
|
response._jsonrpc_error_code = code
|
||||||
|
return response
|
||||||
|
|
||||||
|
def respond(self, result):
|
||||||
|
response = JSONRPCSuccessResponse()
|
||||||
|
|
||||||
|
if not self.unique_id:
|
||||||
|
return None
|
||||||
|
|
||||||
|
response.result = result
|
||||||
|
response.unique_id = self.unique_id
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def _to_dict(self):
|
||||||
|
jdata = {
|
||||||
|
'jsonrpc': JSONRPCProtocol.JSON_RPC_VERSION,
|
||||||
|
'method': self.method,
|
||||||
|
}
|
||||||
|
if self.args:
|
||||||
|
jdata['params'] = self.args
|
||||||
|
if self.kwargs:
|
||||||
|
jdata['params'] = self.kwargs
|
||||||
|
if self.unique_id != None:
|
||||||
|
jdata['id'] = self.unique_id
|
||||||
|
return jdata
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
return json.dumps(self._to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
class JSONRPCBatchRequest(RPCBatchRequest):
|
||||||
|
def create_batch_response(self):
|
||||||
|
if self._expects_response():
|
||||||
|
return JSONRPCBatchResponse()
|
||||||
|
|
||||||
|
def _expects_response(self):
|
||||||
|
for request in self:
|
||||||
|
if isinstance(request, Exception):
|
||||||
|
return True
|
||||||
|
if request.unique_id != None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
return json.dumps([req._to_dict() for req in self])
|
||||||
|
|
||||||
|
|
||||||
|
class JSONRPCBatchResponse(RPCBatchResponse):
|
||||||
|
def serialize(self):
|
||||||
|
return json.dumps([resp._to_dict() for resp in self if resp != None])
|
||||||
|
|
||||||
|
|
||||||
|
class JSONRPCProtocol(RPCBatchProtocol):
|
||||||
|
"""JSONRPC protocol implementation.
|
||||||
|
|
||||||
|
Currently, only version 2.0 is supported."""
|
||||||
|
|
||||||
|
JSON_RPC_VERSION = "2.0"
|
||||||
|
_ALLOWED_REPLY_KEYS = sorted(['id', 'jsonrpc', 'error', 'result'])
|
||||||
|
_ALLOWED_REQUEST_KEYS = sorted(['id', 'jsonrpc', 'method', 'params'])
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(JSONRPCProtocol, self).__init__(*args, **kwargs)
|
||||||
|
self._id_counter = 0
|
||||||
|
|
||||||
|
def _get_unique_id(self):
|
||||||
|
self._id_counter += 1
|
||||||
|
return self._id_counter
|
||||||
|
|
||||||
|
def create_batch_request(self, requests=None):
|
||||||
|
return JSONRPCBatchRequest(requests or [])
|
||||||
|
|
||||||
|
def create_request(self, method, args=None, kwargs=None, one_way=False):
|
||||||
|
if args and kwargs:
|
||||||
|
raise InvalidRequestError('Does not support args and kwargs at '\
|
||||||
|
'the same time')
|
||||||
|
|
||||||
|
request = JSONRPCRequest()
|
||||||
|
|
||||||
|
if not one_way:
|
||||||
|
request.unique_id = self._get_unique_id()
|
||||||
|
|
||||||
|
request.method = method
|
||||||
|
request.args = args
|
||||||
|
request.kwargs = kwargs
|
||||||
|
|
||||||
|
return request
|
||||||
|
|
||||||
|
def parse_reply(self, data):
|
||||||
|
try:
|
||||||
|
rep = json.loads(data)
|
||||||
|
except Exception as e:
|
||||||
|
raise InvalidReplyError(e)
|
||||||
|
|
||||||
|
for k in rep.iterkeys():
|
||||||
|
if not k in self._ALLOWED_REPLY_KEYS:
|
||||||
|
raise InvalidReplyError('Key not allowed: %s' % k)
|
||||||
|
|
||||||
|
if not 'jsonrpc' in rep:
|
||||||
|
raise InvalidReplyError('Missing jsonrpc (version) in response.')
|
||||||
|
|
||||||
|
if rep['jsonrpc'] != self.JSON_RPC_VERSION:
|
||||||
|
raise InvalidReplyError('Wrong JSONRPC version')
|
||||||
|
|
||||||
|
if not 'id' in rep:
|
||||||
|
raise InvalidReplyError('Missing id in response')
|
||||||
|
|
||||||
|
if ('error' in rep) == ('result' in rep):
|
||||||
|
raise InvalidReplyError(
|
||||||
|
'Reply must contain exactly one of result and error.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if 'error' in rep:
|
||||||
|
response = JSONRPCErrorResponse()
|
||||||
|
error = rep['error']
|
||||||
|
response.error = error['message']
|
||||||
|
response._jsonrpc_error_code = error['code']
|
||||||
|
else:
|
||||||
|
response = JSONRPCSuccessResponse()
|
||||||
|
response.result = rep.get('result', None)
|
||||||
|
|
||||||
|
response.unique_id = rep['id']
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def parse_request(self, data):
|
||||||
|
try:
|
||||||
|
req = json.loads(data)
|
||||||
|
except Exception as e:
|
||||||
|
raise JSONRPCParseError()
|
||||||
|
|
||||||
|
if isinstance(req, list):
|
||||||
|
# batch request
|
||||||
|
requests = JSONRPCBatchRequest()
|
||||||
|
for subreq in req:
|
||||||
|
try:
|
||||||
|
requests.append(self._parse_subrequest(subreq))
|
||||||
|
except RPCError as e:
|
||||||
|
requests.append(e)
|
||||||
|
except Exception as e:
|
||||||
|
requests.append(JSONRPCInvalidRequestError())
|
||||||
|
|
||||||
|
if not requests:
|
||||||
|
raise JSONRPCInvalidRequestError()
|
||||||
|
return requests
|
||||||
|
else:
|
||||||
|
return self._parse_subrequest(req)
|
||||||
|
|
||||||
|
def _parse_subrequest(self, req):
|
||||||
|
for k in req.iterkeys():
|
||||||
|
if not k in self._ALLOWED_REQUEST_KEYS:
|
||||||
|
raise JSONRPCInvalidRequestError()
|
||||||
|
|
||||||
|
if req.get('jsonrpc', None) != self.JSON_RPC_VERSION:
|
||||||
|
raise JSONRPCInvalidRequestError()
|
||||||
|
|
||||||
|
if not isinstance(req['method'], basestring):
|
||||||
|
raise JSONRPCInvalidRequestError()
|
||||||
|
|
||||||
|
request = JSONRPCRequest()
|
||||||
|
request.method = str(req['method'])
|
||||||
|
request.unique_id = req.get('id', None)
|
||||||
|
|
||||||
|
params = req.get('params', None)
|
||||||
|
if params != None:
|
||||||
|
if isinstance(params, list):
|
||||||
|
request.args = req['params']
|
||||||
|
elif isinstance(params, dict):
|
||||||
|
request.kwargs = req['params']
|
||||||
|
else:
|
||||||
|
raise JSONRPCInvalidParamsError()
|
||||||
|
|
||||||
|
return request
|
71
ryu/contrib/tinyrpc/server/__init__.py
Normal file
71
ryu/contrib/tinyrpc/server/__init__.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# FIXME: needs unittests
|
||||||
|
# FIXME: needs checks for out-of-order, concurrency, etc as attributes
|
||||||
|
from tinyrpc.exc import RPCError
|
||||||
|
|
||||||
|
class RPCServer(object):
|
||||||
|
"""High level RPC server.
|
||||||
|
|
||||||
|
:param transport: The :py:class:`~tinyrpc.transports.RPCTransport` to use.
|
||||||
|
:param protocol: The :py:class:`~tinyrpc.RPCProtocol` to use.
|
||||||
|
:param dispatcher: The :py:class:`~tinyrpc.dispatch.RPCDispatcher` to use.
|
||||||
|
"""
|
||||||
|
def __init__(self, transport, protocol, dispatcher):
|
||||||
|
self.transport = transport
|
||||||
|
self.protocol = protocol
|
||||||
|
self.dispatcher = dispatcher
|
||||||
|
|
||||||
|
def serve_forever(self):
|
||||||
|
"""Handle requests forever.
|
||||||
|
|
||||||
|
Starts the server loop in which the transport will be polled for a new
|
||||||
|
message.
|
||||||
|
|
||||||
|
After a new message has arrived,
|
||||||
|
:py:func:`~tinyrpc.server.RPCServer._spawn` is called with a handler
|
||||||
|
function and arguments to handle the request.
|
||||||
|
|
||||||
|
The handler function will try to decode the message using the supplied
|
||||||
|
protocol, if that fails, an error response will be sent. After decoding
|
||||||
|
the message, the dispatcher will be asked to handle the resultung
|
||||||
|
request and the return value (either an error or a result) will be sent
|
||||||
|
back to the client using the transport.
|
||||||
|
|
||||||
|
After calling :py:func:`~tinyrpc.server.RPCServer._spawn`, the server
|
||||||
|
will fetch the next message and repeat.
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
context, message = self.transport.receive_message()
|
||||||
|
|
||||||
|
# assuming protocol is threadsafe and dispatcher is theadsafe, as
|
||||||
|
# long as its immutable
|
||||||
|
|
||||||
|
def handle_message(context, message):
|
||||||
|
try:
|
||||||
|
request = self.protocol.parse_request(message)
|
||||||
|
except RPCError as e:
|
||||||
|
response = e.error_respond()
|
||||||
|
else:
|
||||||
|
response = self.dispatcher.dispatch(request)
|
||||||
|
|
||||||
|
# send reply
|
||||||
|
self.transport.send_reply(context, response.serialize())
|
||||||
|
|
||||||
|
self._spawn(handle_message, context, message)
|
||||||
|
|
||||||
|
def _spawn(self, func, *args, **kwargs):
|
||||||
|
"""Spawn a handler function.
|
||||||
|
|
||||||
|
This function is overridden in subclasses to provide concurrency.
|
||||||
|
|
||||||
|
In the base implementation, it simply calls the supplied function
|
||||||
|
``func`` with ``*args`` and ``**kwargs``. This results in a
|
||||||
|
single-threaded, single-process, synchronous server.
|
||||||
|
|
||||||
|
:param func: A callable to call.
|
||||||
|
:param args: Arguments to ``func``.
|
||||||
|
:param kwargs: Keyword arguments to ``func``.
|
||||||
|
"""
|
||||||
|
func(*args, **kwargs)
|
13
ryu/contrib/tinyrpc/server/gevent.py
Normal file
13
ryu/contrib/tinyrpc/server/gevent.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
import gevent
|
||||||
|
|
||||||
|
from . import RPCServer
|
||||||
|
|
||||||
|
|
||||||
|
class RPCServerGreenlets(RPCServer):
|
||||||
|
# documentation in docs because of dependencies
|
||||||
|
def _spawn(self, func, *args, **kwargs):
|
||||||
|
gevent.spawn(func, *args, **kwargs)
|
115
ryu/contrib/tinyrpc/transports/INTEGRATE_ME.py
Normal file
115
ryu/contrib/tinyrpc/transports/INTEGRATE_ME.py
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import gevent
|
||||||
|
import zmq.green as zmq
|
||||||
|
from logbook import Logger
|
||||||
|
|
||||||
|
from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
|
||||||
|
from tinyrpc.dispatch import RPCDispatcher
|
||||||
|
from tinyrpc import RPCError, ServerError, MethodNotFoundError
|
||||||
|
|
||||||
|
|
||||||
|
class Server(object):
|
||||||
|
def __init__(transport, protocol, dispatcher):
|
||||||
|
self.transport = transport
|
||||||
|
self.protocol = protocol
|
||||||
|
self.dispatcher = dispatcher
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
context, message = self.transport.receive_message()
|
||||||
|
except Exception as e:
|
||||||
|
self.exception(e)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# assuming protocol is threadsafe and dispatcher is theadsafe, as long
|
||||||
|
# as its immutable
|
||||||
|
|
||||||
|
self.handle_client(context, message)
|
||||||
|
|
||||||
|
def handle_client(self, context, message):
|
||||||
|
try:
|
||||||
|
request = self.protocol.parse_request(message)
|
||||||
|
except RPCError as e:
|
||||||
|
self.exception(e)
|
||||||
|
response = e.error_respond()
|
||||||
|
else:
|
||||||
|
response = dispatcher.dispatch(request)
|
||||||
|
|
||||||
|
# send reply
|
||||||
|
reply = response.serialize()
|
||||||
|
self.transport.send_reply(context, reply)
|
||||||
|
|
||||||
|
|
||||||
|
class ConcurrentServerMixin(object):
|
||||||
|
def handle_client(self, context, message):
|
||||||
|
self.spawn(
|
||||||
|
super(ConcurrentServer, self).handle_client, context, message
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ZmqRouterTransport(object):
|
||||||
|
def __init__(self, socket):
|
||||||
|
self.socket = socket
|
||||||
|
|
||||||
|
def receive_message(self):
|
||||||
|
msg = socket.recv_multipart()
|
||||||
|
return msg[:-1], [-1]
|
||||||
|
|
||||||
|
def send_reply(self, context, reply):
|
||||||
|
self.send_multipart(context + [reply])
|
||||||
|
|
||||||
|
|
||||||
|
class GeventConcurrencyMixin(ConcurrentServerMixin):
|
||||||
|
def spawn(self, func, *args, **kwargs):
|
||||||
|
gevent.spawn(func, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def rpc_server(socket, protocol, dispatcher):
|
||||||
|
log = Logger('rpc_server')
|
||||||
|
log.debug('starting up...')
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
message = socket.recv_multipart()
|
||||||
|
except Exception as e:
|
||||||
|
log.warning('Failed to receive message from client, ignoring...')
|
||||||
|
log.exception(e)
|
||||||
|
continue
|
||||||
|
|
||||||
|
log.debug('Received message %s from %r' % (message[-1], message[0]))
|
||||||
|
|
||||||
|
# assuming protocol is threadsafe and dispatcher is theadsafe, as long
|
||||||
|
# as its immutable
|
||||||
|
|
||||||
|
def handle_client(message):
|
||||||
|
try:
|
||||||
|
request = protocol.parse_request(message[-1])
|
||||||
|
except RPCError as e:
|
||||||
|
log.exception(e)
|
||||||
|
response = e.error_respond()
|
||||||
|
else:
|
||||||
|
response = dispatcher.dispatch(request)
|
||||||
|
log.debug('Response okay: %r' % response)
|
||||||
|
|
||||||
|
# send reply
|
||||||
|
message[-1] = response.serialize()
|
||||||
|
log.debug('Replying %s to %r' % (message[-1], message[0]))
|
||||||
|
socket.send_multipart(message)
|
||||||
|
|
||||||
|
gevent.spawn(handle_client, message)
|
||||||
|
|
||||||
|
|
||||||
|
context = zmq.Context()
|
||||||
|
socket = context.socket(zmq.ROUTER)
|
||||||
|
socket.bind("tcp://127.0.0.1:12345")
|
||||||
|
|
||||||
|
dispatcher = RPCDispatcher()
|
||||||
|
|
||||||
|
@dispatcher.public
|
||||||
|
def throw_up():
|
||||||
|
return 'asad'
|
||||||
|
raise Exception('BLARGH')
|
||||||
|
|
||||||
|
rpc_server(socket, JSONRPCProtocol(), dispatcher)
|
52
ryu/contrib/tinyrpc/transports/__init__.py
Normal file
52
ryu/contrib/tinyrpc/transports/__init__.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
class ServerTransport(object):
|
||||||
|
"""Base class for all server transports."""
|
||||||
|
|
||||||
|
def receive_message(self):
|
||||||
|
"""Receive a message from the transport.
|
||||||
|
|
||||||
|
Blocks until another message has been received. May return a context
|
||||||
|
opaque to clients that should be passed on
|
||||||
|
:py:func:`~tinyrpc.transport.Transport.send_reply` to identify the
|
||||||
|
client later on.
|
||||||
|
|
||||||
|
:return: A tuple consisting of ``(context, message)``.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def send_reply(self, context, reply):
|
||||||
|
"""Sends a reply to a client.
|
||||||
|
|
||||||
|
The client is usually identified by passing ``context`` as returned
|
||||||
|
from the original
|
||||||
|
:py:func:`~tinyrpc.transport.Transport.receive_message` call.
|
||||||
|
|
||||||
|
Messages must be strings, it is up to the sender to convert the
|
||||||
|
beforehand. A non-string value raises a :py:exc:`TypeError`.
|
||||||
|
|
||||||
|
:param context: A context returned by
|
||||||
|
:py:func:`~tinyrpc.transport.Transport.receive_message`.
|
||||||
|
:param reply: A string to send back as the reply.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class ClientTransport(object):
|
||||||
|
"""Base class for all client transports."""
|
||||||
|
|
||||||
|
def send_message(self, message, expect_reply=True):
|
||||||
|
"""Send a message to the server and possibly receive a reply.
|
||||||
|
|
||||||
|
Sends a message to the connected server.
|
||||||
|
|
||||||
|
Messages must be strings, it is up to the sender to convert the
|
||||||
|
beforehand. A non-string value raises a :py:exc:`TypeError`.
|
||||||
|
|
||||||
|
This function will block until one reply has been received.
|
||||||
|
|
||||||
|
:param message: A string to send.
|
||||||
|
:return: A string containing the server reply.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
31
ryu/contrib/tinyrpc/transports/http.py
Normal file
31
ryu/contrib/tinyrpc/transports/http.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from Queue import Queue
|
||||||
|
import threading
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from . import ServerTransport, ClientTransport
|
||||||
|
|
||||||
|
|
||||||
|
class HttpPostClientTransport(ClientTransport):
|
||||||
|
"""HTTP POST based client transport.
|
||||||
|
|
||||||
|
Requires :py:mod:`requests`. Submits messages to a server using the body of
|
||||||
|
an ``HTTP`` ``POST`` request. Replies are taken from the responses body.
|
||||||
|
|
||||||
|
:param endpoint: The URL to send ``POST`` data to.
|
||||||
|
:param kwargs: Additional parameters for :py:func:`requests.post`.
|
||||||
|
"""
|
||||||
|
def __init__(self, endpoint, **kwargs):
|
||||||
|
self.endpoint = endpoint
|
||||||
|
self.request_kwargs = kwargs
|
||||||
|
|
||||||
|
def send_message(self, message, expect_reply=True):
|
||||||
|
if not isinstance(message, str):
|
||||||
|
raise TypeError('str expected')
|
||||||
|
|
||||||
|
r = requests.post(self.endpoint, data=message, **self.request_kwargs)
|
||||||
|
|
||||||
|
if expect_reply:
|
||||||
|
return r.content
|
52
ryu/contrib/tinyrpc/transports/tcp.py
Normal file
52
ryu/contrib/tinyrpc/transports/tcp.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from Queue import Queue
|
||||||
|
import struct
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from SocketServer import TCPServer, BaseRequestHandler, ThreadingMixIn
|
||||||
|
|
||||||
|
from . import RPCRequestResponseServer
|
||||||
|
|
||||||
|
|
||||||
|
def _read_length_prefixed_msg(sock, prefix_format='!I'):
|
||||||
|
prefix_bytes = struct.calcsize(prefix_format)
|
||||||
|
|
||||||
|
sock.recv(prefix_bytes)
|
||||||
|
|
||||||
|
def _read_n_bytes(sock, n):
|
||||||
|
buf = []
|
||||||
|
while n > 0:
|
||||||
|
data = sock.recv(n)
|
||||||
|
n -= len(data)
|
||||||
|
buf.append(data)
|
||||||
|
|
||||||
|
return ''.join(buf)
|
||||||
|
|
||||||
|
|
||||||
|
def create_length_prefixed_tcp_handler():
|
||||||
|
queue = Queue()
|
||||||
|
class LengthPrefixedTcpHandler(BaseRequestHandler):
|
||||||
|
def handle(self):
|
||||||
|
#msg = _read_length_prefixed_msg(self.request)
|
||||||
|
# this will run inside a new thread
|
||||||
|
self.request.send("hello\n")
|
||||||
|
while True:
|
||||||
|
b = _read_n_bytes(self.request, 10)
|
||||||
|
self.request.send("you sent: %s" % b)
|
||||||
|
queue.put(b)
|
||||||
|
|
||||||
|
return queue, LengthPrefixedTcpHandler
|
||||||
|
|
||||||
|
|
||||||
|
def tcp_test_main():
|
||||||
|
class Server(ThreadingMixIn, TCPServer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
queue, Handler = create_length_prefixed_tcp_handler()
|
||||||
|
|
||||||
|
server = Server(('localhost', 12345), Handler)
|
||||||
|
server.allow_reuse_address = True
|
||||||
|
|
||||||
|
server.serve_forever()
|
90
ryu/contrib/tinyrpc/transports/wsgi.py
Normal file
90
ryu/contrib/tinyrpc/transports/wsgi.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import Queue
|
||||||
|
|
||||||
|
from werkzeug.wrappers import Response, Request
|
||||||
|
|
||||||
|
from . import ServerTransport
|
||||||
|
|
||||||
|
|
||||||
|
class WsgiServerTransport(ServerTransport):
|
||||||
|
"""WSGI transport.
|
||||||
|
|
||||||
|
Requires :py:mod:`werkzeug`.
|
||||||
|
|
||||||
|
Due to the nature of WSGI, this transport has a few pecularities: It must
|
||||||
|
be run in a thread, greenlet or some other form of concurrent execution
|
||||||
|
primitive.
|
||||||
|
|
||||||
|
This is due to
|
||||||
|
:py:func:`~tinyrpc.transports.wsgi.WsgiServerTransport.handle` blocking
|
||||||
|
while waiting for a call to
|
||||||
|
:py:func:`~tinyrpc.transports.wsgi.WsgiServerTransport.send_reply`.
|
||||||
|
|
||||||
|
The parameter ``queue_class`` must be used to supply a proper queue class
|
||||||
|
for the chosen concurrency mechanism (i.e. when using :py:mod:`gevent`,
|
||||||
|
set it to :py:class:`gevent.queue.Queue`).
|
||||||
|
|
||||||
|
:param max_content_length: The maximum request content size allowed. Should
|
||||||
|
be set to a sane value to prevent DoS-Attacks.
|
||||||
|
:param queue_class: The Queue class to use.
|
||||||
|
:param allow_origin: The ``Access-Control-Allow-Origin`` header. Defaults
|
||||||
|
to ``*`` (so change it if you need actual security).
|
||||||
|
"""
|
||||||
|
def __init__(self, max_content_length=4096, queue_class=Queue.Queue,
|
||||||
|
allow_origin='*'):
|
||||||
|
self._queue_class = queue_class
|
||||||
|
self.messages = queue_class()
|
||||||
|
self.max_content_length = max_content_length
|
||||||
|
self.allow_origin = allow_origin
|
||||||
|
|
||||||
|
def receive_message(self):
|
||||||
|
return self.messages.get()
|
||||||
|
|
||||||
|
def send_reply(self, context, reply):
|
||||||
|
if not isinstance(reply, str):
|
||||||
|
raise TypeError('str expected')
|
||||||
|
|
||||||
|
context.put(reply)
|
||||||
|
|
||||||
|
def handle(self, environ, start_response):
|
||||||
|
"""WSGI handler function.
|
||||||
|
|
||||||
|
The transport will serve a request by reading the message and putting
|
||||||
|
it into an internal buffer. It will then block until another
|
||||||
|
concurrently running function sends a reply using
|
||||||
|
:py:func:`~tinyrpc.transports.WsgiServerTransport.send_reply`.
|
||||||
|
|
||||||
|
The reply will then be sent to the client being handled and handle will
|
||||||
|
return.
|
||||||
|
"""
|
||||||
|
request = Request(environ)
|
||||||
|
request.max_content_length = self.max_content_length
|
||||||
|
|
||||||
|
access_control_headers = {
|
||||||
|
'Access-Control-Allow-Methods': 'POST',
|
||||||
|
'Access-Control-Allow-Origin': self.allow_origin,
|
||||||
|
'Access-Control-Allow-Headers': \
|
||||||
|
'Content-Type, X-Requested-With, Accept, Origin'
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.method == 'OPTIONS':
|
||||||
|
response = Response(headers=access_control_headers)
|
||||||
|
|
||||||
|
elif request.method == 'POST':
|
||||||
|
# message is encoded in POST, read it...
|
||||||
|
msg = request.stream.read()
|
||||||
|
|
||||||
|
# create new context
|
||||||
|
context = self._queue_class()
|
||||||
|
|
||||||
|
self.messages.put((context, msg))
|
||||||
|
|
||||||
|
# ...and send the reply
|
||||||
|
response = Response(context.get(), headers=access_control_headers)
|
||||||
|
else:
|
||||||
|
# nothing else supported at the moment
|
||||||
|
response = Response('Only POST supported', 405)
|
||||||
|
|
||||||
|
return response(environ, start_response)
|
76
ryu/contrib/tinyrpc/transports/zmq.py
Normal file
76
ryu/contrib/tinyrpc/transports/zmq.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import absolute_import # needed for zmq import
|
||||||
|
import zmq
|
||||||
|
|
||||||
|
from . import ServerTransport, ClientTransport
|
||||||
|
|
||||||
|
|
||||||
|
class ZmqServerTransport(ServerTransport):
|
||||||
|
"""Server transport based on a :py:const:`zmq.ROUTER` socket.
|
||||||
|
|
||||||
|
:param socket: A :py:const:`zmq.ROUTER` socket instance, bound to an
|
||||||
|
endpoint.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, socket):
|
||||||
|
self.socket = socket
|
||||||
|
|
||||||
|
def receive_message(self):
|
||||||
|
msg = self.socket.recv_multipart()
|
||||||
|
return msg[:-1], msg[-1]
|
||||||
|
|
||||||
|
def send_reply(self, context, reply):
|
||||||
|
self.socket.send_multipart(context + [reply])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, zmq_context, endpoint):
|
||||||
|
"""Create new server transport.
|
||||||
|
|
||||||
|
Instead of creating the socket yourself, you can call this function and
|
||||||
|
merely pass the :py:class:`zmq.core.context.Context` instance.
|
||||||
|
|
||||||
|
By passing a context imported from :py:mod:`zmq.green`, you can use
|
||||||
|
green (gevent) 0mq sockets as well.
|
||||||
|
|
||||||
|
:param zmq_context: A 0mq context.
|
||||||
|
:param endpoint: The endpoint clients will connect to.
|
||||||
|
"""
|
||||||
|
socket = zmq_context.socket(zmq.ROUTER)
|
||||||
|
socket.bind(endpoint)
|
||||||
|
return cls(socket)
|
||||||
|
|
||||||
|
|
||||||
|
class ZmqClientTransport(ClientTransport):
|
||||||
|
"""Client transport based on a :py:const:`zmq.REQ` socket.
|
||||||
|
|
||||||
|
:param socket: A :py:const:`zmq.REQ` socket instance, connected to the
|
||||||
|
server socket.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, socket):
|
||||||
|
self.socket = socket
|
||||||
|
|
||||||
|
def send_message(self, message, expect_reply=True):
|
||||||
|
self.socket.send(message)
|
||||||
|
|
||||||
|
if expect_reply:
|
||||||
|
return self.socket.recv()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, zmq_context, endpoint):
|
||||||
|
"""Create new client transport.
|
||||||
|
|
||||||
|
Instead of creating the socket yourself, you can call this function and
|
||||||
|
merely pass the :py:class:`zmq.core.context.Context` instance.
|
||||||
|
|
||||||
|
By passing a context imported from :py:mod:`zmq.green`, you can use
|
||||||
|
green (gevent) 0mq sockets as well.
|
||||||
|
|
||||||
|
:param zmq_context: A 0mq context.
|
||||||
|
:param endpoint: The endpoint the server is bound to.
|
||||||
|
"""
|
||||||
|
socket = zmq_context.socket(zmq.REQ)
|
||||||
|
socket.connect(endpoint)
|
||||||
|
return cls(socket)
|
@ -7,4 +7,3 @@ paramiko
|
|||||||
routes
|
routes
|
||||||
six>=1.4.0
|
six>=1.4.0
|
||||||
webob>=1.0.8
|
webob>=1.0.8
|
||||||
tinyrpc
|
|
||||||
|
Loading…
Reference in New Issue
Block a user