Skip to content
Snippets Groups Projects
Commit 4eadc7b5 authored by Antoine Millet's avatar Antoine Millet
Browse files

Rewrited sjRpc with low level channel multiplexage and gevent usage.

parent d63729bd
No related branches found
No related tags found
No related merge requests found
#!/usr/bin/env python
#coding:utf8
'''
......@@ -11,14 +9,12 @@ The library is separated into four parts:
* core library contains all common classes.
* server library contains all the server side related stuff.
* client library contains all the client side related stuff.
* utils library contains some helpers used in previous libraries.
'''
import sjrpc.core
import sjrpc.server
import sjrpc.client
import sjrpc.utils
__version__ = 13
__version__ = '14~dev'
#!/usr/bin/env python
#coding:utf8
from sjrpc.client.simple import SimpleRpcClient
__all__ = ('SimpleRpcClient',)
#!/usr/bin/env python
#coding=utf8
import select
import socket
import logging
from sjrpc.core import RpcConnection, ConnectionManager
class SimpleRpcClient(ConnectionManager):
'''
Create a new simple RPC client.
:param connect: the :class:`RpcConnection` object to bind the client manager
:param default_handler: the default handler to bind to the client connection
:param on_disconnect: method on the handler to call when the client
disconnects.
'''
def __init__(self, connection, default_handler=None, on_disconnect=None):
super(SimpleRpcClient, self).__init__()
self._on_disconnect = on_disconnect
self._connection = connection
self._connection.set_handler(default_handler)
self.register(self._connection.get_fd(), self._handle_event)
@classmethod
def from_addr(cls, addr, port, enable_ssl=False, cert=None, timeout=None,
conn_timeout=30.0, default_handler=None, on_disconnect=None):
'''
Construct the instance of :class:`SimpleRpcClient` without providing
the :class:`RpcConnection` object. The :class:`RpcConnection` is
automatically created and passed to the standard constructor before to
return the new instance.
:param addr: the target ip address
:param port: the target port
:param ssl: enable SSL
:param timeout: the global call timeout setting
:param conn_timeout: the connection operation timeout
:param cert: is SSL is enabled, profile the filename of certificate to
check. If None, don't check certificate.
:param default_handler: the default handler to bind to the
client connection
:param on_disconnect: method on the handler to call when the client
disconnects.
'''
connection = RpcConnection.from_addr(addr, port, None, timeout=timeout,
conn_timeout=conn_timeout,
enable_ssl=enable_ssl, cert=cert)
client = cls(connection, default_handler=default_handler,
on_disconnect=on_disconnect)
connection._manager = client
return client
@classmethod
def from_sock(cls, sock, default_handler=None, on_disconnect=None):
'''
Construct the instance of :class:`SimpleRpcClient` without providing
the :class:`RpcConnection` object. The :class:`RpcConnection` is
automatically created and passed to the standard constructor before to
return the new instance
:param sock: the socket object to wrap with :class:`RpcConnection`
object.
:param default_handler: the default handler to bind to the
client connection
:param on_disconnect: method on the handler to call when the client
disconnects.
'''
connection = RpcConnection(sock, None)
client = cls(connection, default_handler, on_disconnect)
connection._manager = client
return client
def shutdown(self):
super(SimpleRpcClient, self).shutdown()
self._connection.shutdown(self._on_disconnect)
def _handle_event(self, fd, events):
if events & select.EPOLLIN:
# Data are ready to be readed on socket
try:
self._connection.receive()
except socket.error as err:
logging.debug('Socket error while receiving from the client '
'fd/%s: %s', fd, err)
self.shutdown()
if events & select.EPOLLOUT:
# Data are ready to be written on socket
try:
self._connection.send()
except socket.error as err:
logging.debug('Socket error while sending to the client '
'fd/%s: %s', fd, err)
self.shutdown()
if events & select.EPOLLHUP:
logging.debug('Socket HUP fd/%s', fd)
self.shutdown()
def all_connections(self):
return set((self._connection,))
def call(self, *args, **kwargs):
return self._connection.call(*args, **kwargs)
......@@ -2,10 +2,9 @@
#coding:utf8
from sjrpc.core.rpcconnection import *
from sjrpc.core.connectionmanagers import *
from sjrpc.core.callers import *
from sjrpc.core.exceptions import *
from sjrpc.core.async import *
__all__ = ('ConnectionManager', 'RpcConnection', 'RpcCaller', 'RpcError',
'ThreadedRpcCaller', )
__all__ = ('RpcConnection', 'RpcCaller', 'ThreadedRpcCaller', 'RpcError',
'AsyncWatcher')
#!/usr/bin/env python
#coding:utf8
import threading
from sjrpc.core.exceptions import RpcError
ERRMSG_RPCERR = ('Unable to send reply to the peer: %s (this error is usualy '
'raised when connection is lost while handler function '
'execution)')
class RpcCaller(object):
'''
A caller execute a callable (function, method, class which implement the
:meth:`__call__` method...) in a particular context (threaded or
"timeouted" for example), and return the result (or the exception) to the
:meth:`__call__` method...) in a particular context (threaded or
"timeouted" for example), and return the result (or the exception) to the
peer through it :class:`RpcConnection` object.
'''
def __init__(self, request, connection, func):
def __init__(self, request, protocol, func):
self._request = request
self._connection = connection
self._protocol = protocol
self._func = func
# Apply the request decorator
#request_decorator = connection.request_decorator
#if request_decorator is not None:
# self._func = request_decorator(self._func)
def run(self):
'''
Run the callable and return the result (or the exception) to the peer.
'''
msg_id = self._request['id']
args = self._request['args']
kwargs = self._request['kwargs']
if not getattr(self._func, '__pure__', False):
args.insert(0, self._connection)
if getattr(self._func, '__pass_rpc__', False):
args.insert(0, self._protocol)
if getattr(self._func, '__pass_connection__', False):
args.insert(0, self._protocol.connection)
try:
returned = self._func(*args, **kwargs)
except Exception as err:
self._connection.error(msg_id, message=str(err),
error=err.__class__.__name__)
try:
self._protocol.error(msg_id, message=str(err),
error=err.__class__.__name__)
except RpcError as err:
self._protocol.connection.logger.error(ERRMSG_RPCERR, err)
else:
self._connection.response(msg_id, returned=returned)
try:
self._protocol.response(msg_id, returned=returned)
except RpcError as err:
self._protocol.connection.logger.error(ERRMSG_RPCERR, err)
def start(self):
'''
Start execution of the callable, the most of time, it just call
Start execution of the callable, the most of time, it just call
:meth:`run` method.
'''
self.run()
class ThreadedRpcCaller(RpcCaller):
'''
A caller which make the call into a separated thread.
'''
def __init__(self, *args, **kwargs):
super(ThreadedRpcCaller, self).__init__(*args, **kwargs)
self._thread = threading.Thread(target=self.run)
self._thread.name = 'Processing of call: %s' % self._request['id']
self._thread.daemon = True
def start(self):
self._thread.start()
#!/usr/bin/env python
#coding:utf8
import select
import threading
class ConnectionManager(object):
'''
Base class for all connection manager classes.
'''
# The timeout to wait before the poll call release the hand with no events:
POLL_TIMEOUT = 1
# Masks for fd registration on poll object:
MASK_NORMAL = (select.EPOLLIN | select.EPOLLPRI |
select.EPOLLERR | select.EPOLLHUP)
MASK_WRITABLE = MASK_NORMAL | select.EPOLLOUT
def __init__(self):
self._poll = select.epoll()
self._running = True
self._received_msg = {}
self._wait_groups = {}
self._poll_callbacks = {}
def register(self, fd, callback, *args, **kwargs):
'''
Register an fd on the poll object with the specified callback. The
callback will be called each time poller drop an event for the specified
fd. Extra args will be passed to the callback after fd and events.
:param fd: the fd to register
:param callback: the callable to use on event
:param *args, **kwargs: extra arguments passed to the callback
'''
if hasattr(fd, 'fileno'):
fd = fd.fileno()
self._poll_callbacks[fd] = {'func': callback,
'extra': args,
'kwextra': kwargs}
self._poll.register(fd, ConnectionManager.MASK_NORMAL)
def unregister(self, fd):
'''
Unregister the specified fd from the manager.
:param fd: the fd to unregister.
'''
self._poll.unregister(fd)
del self._poll_callbacks[fd]
def is_running(self):
return self._running
def run(self):
'''
Run the main loop of the :class:`ConnectionManager`. It will catch
events on registered :class:`RpcConnection` and process them.
'''
while self._running:
try:
events = self._poll.poll(ConnectionManager.POLL_TIMEOUT)
except IOError:
pass
else:
for fd, event in events:
if fd in self._poll_callbacks:
cb = self._poll_callbacks[fd]
cb['func'](fd, event, *cb['extra'], **cb['kwextra'])
def start(self, daemonize=False):
'''
Run the main loop in a separated thread.
:param daemonize: set the thread daemon state
'''
t = threading.Thread(target=self.run)
t.daemon = daemonize
t.start()
def wait(self, msg_id_set, timeout=None, wait_all=True):
'''
Wait for the asynchronous messages in ``msg_id_set``.
When the timeout argument is present and not ``None``, it should be a
floating point number specifying a timeout for the operation in
seconds (or fractions thereof).
You can also set ``wait_all`` to False if you want to unlock the call
when the first response is received.
:param msg_id_set: set of message to wait
:type msg_id_set: :class:`frozenset`
:param timeout: timeout value or None to disable timeout (default: None)
:type timeout: :class:`int` or :class:`None`
:param wait_all: wait for all messages (default: True)
:type wait_all: :class:`bool`
.. warning:
This is important that ``msg_id_set`` is a :class:`frozenset`
and not a :class:`set`.
'''
waiter = {'event': threading.Event(), 'wait_all': wait_all}
self._wait_groups.setdefault(msg_id_set, waiter)
already_completed = self._check_waiter(msg_id_set)
if not already_completed:
waiter['event'].wait(timeout=timeout)
# Clean the call list on each attached RpcConnection
for connection in self.all_connections():
connection.clean_all_calls(msg_id_set)
# Get the messages:
messages = []
for msg_id, msg in self._received_msg.items():
if msg_id in msg_id_set:
messages.append(msg)
del self._received_msg[msg_id]
waiter['responses'] = tuple(messages)
messages = waiter['responses']
del self._wait_groups[msg_id_set]
return messages
def signal_arrival(self, message):
'''
Signal the arrival of a new message to the :class:`ConnectionManager`.
This method is ordinary called by the :class:`RpcConnections` objects,
when a response to an asynchronous call is received.
:param message: the message received
'''
self._received_msg[message['id']] = message
for waitset in self._wait_groups.keys():
self._check_waiter(waitset)
def _check_waiter(self, waitset):
'''
Check if a waitset is completed and process it.
:param waitset: the waitset to check
:return: True if waitset is completed else None
'''
# Make a set of received messages ids:
recv_msg = set(self._received_msg)
try:
waiter = self._wait_groups[waitset]
except KeyError:
return False
is_ok = (waiter['wait_all'] and waitset <= recv_msg
or not waiter['wait_all'] and not recv_msg.isdisjoint(waitset))
if is_ok:
# Unlock the event:
waiter['event'].set()
return True
else:
return False
def all_connections(self):
'''
Return all connection attached to this :class:`ConnectionManager`.
:return: a set of :class:`RpcConnection` attached
to this :class:`ConnectionManager`
'''
raise NotImplementedError
def shutdown(self):
'''
Shutdown the manager properly.
'''
self._running = False
def data_to_write(self, fd):
'''
Method called by a connection to inform the manager that it have data
to send.
:param connection: the fd which have data to write
'''
if fd is not None:
self._poll.modify(fd, ConnectionManager.MASK_WRITABLE)
def nothing_to_write(self, fd):
'''
Method called by a connection to inform the manager that it have no
more data to send.
:param fd: the fd which have no more data to write
'''
if fd is not None:
self._poll.modify(fd, ConnectionManager.MASK_NORMAL)
def handle_event(self, fd, event):
'''
Handle an event and make associated action. This is an abstract method to
overload on derived classes.
:param fd: the fd that have generated the event
:param event: the event as returned by the poller object
'''
pass
#!/usr/bin/env python
#coding:utf8
'''
Contains sjRpc exceptions.
'''
class RpcError(Exception):
'''
Exception raised by caller when an error occurs while execution of remote
procedure call.
'''
def __init__(self, exception, message):
self.exception = exception
self.message = message
def __str__(self):
return '%s' % self.message
class SocketRpcError(Exception):
'''
Exception used internally to raise a socket fault.
'''
import json
import logging
from uuid import uuid4
from threading import Event
from sjrpc.core.callers import RpcCaller, ThreadedRpcCaller
from sjrpc.core.exceptions import RpcError
class RpcProtocol(object):
REQUEST_MESSAGE = {'id': None, 'method': None, 'args': [], 'kwargs': {}}
RESPONSE_MESSAGE = {'id': None, 'return': None, 'error': None}
'''
:param connection: the connection serving this :class:`RpcProtocol`
:param label: the label of this :class:`RpcProtocol` instance
:param handler: command handler to bind by default
:param on_disconnect: callback called when client disconnect
:param request_decorator: decorator applied on each handler function
:param timeout: global command timeout
:param logger: logging module :class:`Logger` instance
'''
def __init__(self, connection, label, handler=None, on_disconnect=None,
request_decorator=None, timeout=30, logger=None):
self._connection = connection
self._label = label
self._handler = handler
self._on_disconnect = on_disconnect
self.request_decorator = request_decorator
self._call_timeout = timeout
if logger is None:
logger_name = '%s.protos.%s' % (connection.logger.name, label)
self.logger = logging.getLogger(logger_name)
else:
self.logger = logger
# Store all calls sent to the peer. Key is the id of the call and value
# the event to raise when call is finished.
self._calls = {}
@property
def connection(self):
return self._connection
def handle(self, label, size):
'''
Decode json, and dispatch message.
'''
buf = self._connection.recv_until(size)
msg = json.loads(buf)
self._dispatch(msg)
def shutdown(self):
# Release all waiting calls from this rpc:
for cid, call in self._calls.iteritems():
err = {'exception': 'RpcError',
'message': 'Connection reset by peer'}
if 'event' in call:
call['error'] = err
call['return'] = None
call['event'].set()
else:
msg = {'id': cid, 'error': err, 'return': None}
self._manager.signal_arrival(msg)
# Execute on_disconnect callback:
callback = None
if self._on_disconnect is not None and not callable(self._on_disconnect):
if self._handler is not None:
try:
callback = self._handler[self._on_disconnect]
except KeyError:
self.logger.warn('Shutdown callback not found in current '
'rpc attached handler, ignoring')
callback = None
else:
self.logger.warn('Shutdown callback specified but no handler '
'binded on rpc, ignoring')
callback = None
if callback is not None:
try:
callback(self._connection)
except Exception as err:
self.logger.debug('Error while execution of shutdown '
'callback: %s', err)
def get_handler(self):
'''
Return the handler binded to the :class:`RpcConnection`.
:return: binded handler
'''
return self._handler
def set_handler(self, handler):
'''
Define a new handler for this connection.
:param handler: the new handler to define.
'''
self._handler = handler
def _dispatch(self, message):
'''
Dispatch a received message according to it type.
:param message: the received message to dispatch
.. note::
When dispatching a call, the responsability of response is delegated
to the caller, except for the case where the method isn't found on
the handler.
'''
self.logger.debug('Received: %s', message)
if set(RpcProtocol.REQUEST_MESSAGE) <= set(message):
self._handle_request(message)
elif set(RpcProtocol.RESPONSE_MESSAGE) <= set(message):
self._handle_response(message)
else:
self.logger.debug('Malformed message received: %s', message)
def _handle_request(self, message):
'''
Handle an inbound request message.
'''
if self._handler is not None:
try:
func = self._handler[message['method']]
except KeyError:
self.error(message['id'], 'NameError',
"remote name '%s' is not defined" % message['method'])
else:
if getattr(func, '__threaded__', True):
ThreadedRpcCaller(message, self, func).start()
else:
RpcCaller(message, self, func).start()
else:
self.error(message['id'], 'NameError',
"remote name '%s' is not defined" % message['method'])
def _handle_response(self, message):
'''
Handle an inbound response message
'''
# Handle response message from the peer:
call = self._calls.get(message['id'])
if call is not None:
# Call exists in call list
if message['error'] is None:
call['return'] = message['return']
else:
call['error'] = message['error']
if 'event' in call:
# Release the call if its synchronous:
call['event'].set()
else:
# Else, it's an asynchonous call, we need to push the answer
# on the queue:
queue = call['queue']
del call['queue']
queue.put(call)
# Finally, delete the call from the current running call list:
del self._calls[message['id']]
def _send(self, message):
'''
Low level method to encode a message in json, calculate it size, and
place result on outbound buffer.
.. warning::
Message must be a jsonisable structure.
'''
#if not self._connected: #FIXME
# raise RpcError('SendError', 'disconnected from the peer')
self.logger.debug('Sending: %s', message)
json_msg = json.dumps(message)
self._connection.send(self._label, payload=json_msg)
def _send_call(self, method_name, *args, **kwargs):
'''
Create the message for the call and push them to the outbound queue.
:param method_name: the name of the method to call on the peer
:param *args: arguments to pass to the remote method
:param **kwargs: keyword arguments to pass to the remote method
:return: the generated id for the request
:rtype: :class:`str` object
'''
msg = RpcProtocol.REQUEST_MESSAGE.copy()
msg['method'] = method_name
msg['args'] = args
msg['kwargs'] = kwargs
msg['id'] = str(uuid4())
self._send(msg)
return msg['id']
def _send_response(self, msg_id, returned=None, error=None):
'''
Low level method to send a response message to the peer.
:param msg_id: the id of the replied message
:param returned: returned data
:type returned: returned data or None if errors have been raised
:param error: raised errors
:type error: raised error or None if no error have been raised
'''
msg = RpcProtocol.RESPONSE_MESSAGE.copy()
msg['id'] = msg_id
msg['return'] = returned
msg['error'] = error
self._send(msg)
def response(self, msg_id, returned):
'''
Send an "return" response to the peer.
:param msg_id: the id of the replied message
:param returned: the value returned by the function
.. warning::
In case of raised error, use the :meth:`error` method instead of
this one.
'''
self._send_response(msg_id, returned=returned)
def error(self, msg_id, error, message, traceback=None):
'''
Send an error response to the peer.
:param msg_id: the id of the replied message
:param error: the name of the raised exception
:param message: human readable error for the exception
'''
err = {'exception': error, 'message': message}
self._send_response(msg_id, error=err)
def call(self, method_name, *args, **kwargs):
'''
Make a new remote call on the peer.
:param method_name: the method to call on the peer
:param \*args: the arguments for the call
:param \*\*kwargs: the keyword arguments for the call
:return: the data returned by the peer for the call
.. note::
This function will block until the peer response is received. You
can also specify a ``timeout`` argument to specify a number of
seconds before to raise an :exc:`CallTimeout` exception if the peer
didnt respond.
'''
if '_timeout' in kwargs:
timeout = kwargs['_timeout']
del kwargs['_timeout']
else:
timeout = self._call_timeout
# Send the call to the peer:
msg_id = self._send_call(method_name, *args, **kwargs)
# Create an item in calls dict with reference to the event to raise:
call = {'return': None, 'error': None, 'event': Event(), 'id': msg_id}
self._calls[msg_id] = call
# Wait for the response:
call['event'].wait(timeout)
# Check if timeout occured:
if not call['event'].is_set():
raise RpcError('TimeoutError', 'remote method timeout')
# Check if error occured while execution:
if call['error'] is not None:
raise RpcError(call['error']['exception'],
call['error']['message'])
return call['return']
def async_call(self, queue, method_name, *args, **kwargs):
'''
Make a new asynchronous call on the peer.
:param queue: the queue where to push the response when received
:param method_name: the method to call on the peer
:param _data: local data to give back on the response
:param \*args: the arguments for the call
:param \*\*kwargs: the keyword arguments for the call
:return: the message id of the call
'''
# Extract _data from argument:
if '_data' in kwargs:
data = kwargs['_data']
del kwargs['_data']
else:
data = None
# Send the call to the peer:
msg_id = self._send_call(method_name, *args, **kwargs)
# Register the call but don't wait for the response:
self._calls[msg_id] = {'id': msg_id, 'async': True,
'data': data, 'queue': queue}
return msg_id
This diff is collapsed.
#!/usr/bin/env python
#coding:utf8
from sjrpc.server.simple import *
from __future__ import absolute_import
__all__ = ('SimpleRpcServer', 'SimpleSslRpcServer')
from sjrpc.server.simple import GreenRpcServer, SSLGreenRpcServer
__all__ = ('GreenRpcServer', 'SSLGreenRpcServer')
#!/usr/bin/env python
#coding=utf8
import ssl
import time
import socket
import select
import logging
from sjrpc.core import RpcConnection, ConnectionManager
from sjrpc.core import RpcConnection, GreenRpcConnection
class RpcServer(object):
'''
Base class for all RpcServer classes.
'''
def __init__(self):
self._clients = set()
self.logger = logging.getLogger('sjrpc')
def register(self, conn):
'''
Register a new connection on this server.
:param conn: the connection to register.
'''
self._clients.add(conn)
def unregister(self, conn, shutdown=False):
'''
Unregister the specified client from this server. If shutdown is
specified, client is shutdown before to be unregistered.
:param conn: the connection to unregister
:param shutdown: shutdown or not the connection before to register
'''
if conn in self._clients:
if shutdown:
conn.shutdown()
self._clients.remove(conn)
def run(self):
raise NotImplementedError('You must use a sub-class of RpcServer.')
def shutdown(self):
'''
Shutdown the :class:`RpcServer` instance.
'''
self.logger.info('Shutdown requested')
for client in self._clients.copy():
self.unregister(client, shutdown=True)
class GreenRpcServer(RpcServer):
class SimpleRpcServer(ConnectionManager):
'''
A simple RPC Server that wait for new connections and dispatch messages
from thoses are already established.
:param sock: the :class:`socket` object to bind to the server connection
:param default_handler: the default handler to bind to the new client
connections
An sjrpc server that use Gevent and its Greenlets to handle client
connections.
:param addr: the ip address to connect to
:param port: the tcp port to connect to
:param conn_args, conn_kw: the arguments to pass to the client
:class:`RpcConnection` instance
.. note::
At this time, the server must be ran into that imported this module.
This is a limitation of Gevent 0.x and this should be disappear when
Gevent will be used.
'''
def __init__(self, sock, default_handler=None, on_disconnect=None):
super(SimpleRpcServer, self).__init__()
sock.setblocking(False)
self._listening_sock = sock
self.register(sock, self._handle_master_event)
self._clients = {}
self._default_handler = default_handler
self._on_disconnect = on_disconnect
def _accept_connection(self):
return self._listening_sock.accept()
def __init__(self, addr, port, conn_args=(), conn_kw={}, *args, **kwargs):
from gevent.server import StreamServer
super(GreenRpcServer, self).__init__(*args, **kwargs)
self._conn_args = conn_args
self._conn_kw = conn_kw
self._server = StreamServer((addr, port), self._handler)
def _handler(self, sock, address):
conn = GreenRpcConnection(sock, *self._conn_args, **self._conn_kw)
self.register(conn)
RpcConnection.run(conn) #FIXME
def run(self):
#self._server.serve_forever()
#FIXME: Sometime, when shutdown is called, _server.serve_forever stay
# stuck and never return. This is maybe a problem with gevent,
# but this workaround seem to work.
self._server.start()
while not self._server._stopped_event.is_set():
self._server._stopped_event.wait(2)
def shutdown(self):
super(SimpleRpcServer, self).shutdown()
time.sleep(ConnectionManager.POLL_TIMEOUT)
for connection in self._clients.values():
connection.shutdown(self._on_disconnect)
self._listening_sock.close()
def shutdown_client(self, fd):
conn = self._clients.get(fd)
try:
self.unregister(fd)
except IOError:
pass
if fd is not None:
try:
del self._clients[fd]
except KeyError:
pass
if conn is not None:
conn.shutdown(callback=self._on_disconnect)
def all_connections(self):
return set(self._clients.values())
def _handle_master_event(self, fd, events):
# Event concerns the listening socket:
if events & select.EPOLLIN:
accepted = self._accept_connection()
if accepted is not None:
sock, address = accepted
sock.setblocking(False)
connection = RpcConnection(sock, self,
handler=self._default_handler)
self.register(connection.get_fd(), self._handle_client_event)
self._clients[connection.get_fd()] = connection
def _handle_client_event(self, fd, events):
connection = self._clients[fd]
if events & select.EPOLLIN:
# Data are ready to be readed on socket
try:
connection.receive()
except socket.error as err:
logging.debug('Socket error while receiving from client '
'fd/%s: %s', fd, err)
self.shutdown_client(fd)
except Exception as err:
logging.debug('Unknown error while receiving from client '
'fd/%s: %s', fd, err)
self.shutdown_client(fd)
if events & select.EPOLLOUT:
# Data are ready to be written on socket
try:
connection.send()
except socket.error as err:
logging.debug('Socket error while sending to the client '
'fd/%s: %s', fd, err)
self.shutdown_client(fd)
except Exception as err:
logging.debug('Unknown error while sending to the client '
'fd/%s: %s', fd, err)
self.shutdown_client(fd)
if events & select.EPOLLHUP:
logging.debug('Socket HUP fd/%s', fd)
self.shutdown_client(fd)
class SimpleSslRpcServer(SimpleRpcServer):
super(GreenRpcServer, self).shutdown()
self._server.stop()
class SSLGreenRpcServer(GreenRpcServer):
'''
A simple RPC Server that wait for new connections and dispatch messages
from thoses are already established. This server version enable SSL on
client sockets.
:param sock: the :class:`socket` object to bind to the server connection
:param default_handler: the default handler to bind to the new client
connections
The SSL version of :class:`GreenRpcServer`. All connecting client are
automatically wrapped into and SSL connection.
You must provide certfile and keyfile to make this work properly.
'''
def __init__(self, sock, certfile=None, keyfile=None, **kwargs):
self._certfile = certfile
self._keyfile = keyfile
super(SimpleSslRpcServer, self).__init__(sock, **kwargs)
def _accept_connection(self):
sock, address = self._listening_sock.accept()
try:
sslsock = ssl.wrap_socket(sock, server_side=True,
keyfile=self._keyfile,
certfile=self._certfile,
ssl_version=ssl.PROTOCOL_TLSv1,
do_handshake_on_connect=True)
except ssl.SSLError as err:
logging.debug('Error when accepting ssl connection: %s', err)
else:
return sslsock, address
def __init__(self, addr, port, conn_args=(), conn_kw={}, certfile=None,
keyfile=None, *args, **kw):
super(GreenRpcServer, self).__init__(*args, **kw)
from gevent.server import StreamServer
self._conn_args = conn_args
self._conn_kw = conn_kw
self._server = StreamServer((addr, port), self._handler,
certfile=certfile, keyfile=keyfile)
#!/usr/bin/env python
#coding:utf8
from sjrpc.utils.datastructures import *
from sjrpc.utils.proxies import *
from sjrpc.utils.handlers import *
__all__ = ('BytesBuffer', 'ConnectionProxy', 'RpcHandler', 'threadless', 'pure')
__all__ = ('ConnectionProxy', 'RpcHandler', 'threadless', 'pure',
'pass_connection', 'pass_rpc')
#!/usr/bin/env python
#coding:utf8
import threading
class BytesBuffer(object):
'''
BytesBuffers objects are useful to create socket buffers.
Its behavior is pretty simple and looks like a FIFO queue: you can push data
on it with push method, and pull a specified amount of data.
.. note::
All calls to :class:`BytesBuffer` objects are thread-safe.
'''
def __init__(self, data=None):
if data is None:
self._buf = ''
else:
self._buf = data
self._lock = threading.RLock()
def push(self, data):
'''
Push data on buffer.
:param data: the data to push on the buffer
:type data: :class:`str` object
'''
self._lock.acquire()
self._buf += data
self._lock.release()
def pull(self, size=0):
'''
Pull the specified amount of data on the buffer.
If size is equal to 0, will return the entire content. If buffer
contains less of data than asked, :meth:`pull` will return all the
available data.
:param size: size (in bytes) of data to pull on the buffer
:type size: 0 or positive integer
:return: asked data
:rtype: :class:`str` object
'''
self._lock.acquire()
if size == 0:
buf = self._buf
self._buf = ''
else:
buf = self._buf[:size]
self._buf = self._buf[size:]
self._lock.release()
return buf
def __len__(self):
'''
Return the size of the buffer.
'''
return len(self._buf)
def __enter__(self):
return self._lock.__enter__()
def __exit__(self, exc_type, exc_value, traceback):
return self._lock.__exit__(exc_type, exc_value, traceback)
......@@ -24,15 +24,40 @@ def threadless(func):
Function handler decorator -- don't spawn a new thread when function is
called.
'''
func.__threaded__ = False
return func
def pure(func):
'''
Function handler decorator -- the function is a pure fonction, caller will
not pass :class:`RpcConnection` object as first call parameters.
.. note::
This decorator is useless since the default behavior have change. You
can use :func:`pass_connection` decorator to do the opposite.
This function is kept for compatibility only, and will be removed latter.
'''
return func
def pass_connection(func):
'''
Function handler decorator -- pass on first parameter the connection which
called this function.
'''
func.__pass_connection__ = True
return func
def pass_rpc(func):
'''
func.__pure__ = True
Function handler decorator -- pass on first (or after connection) the
rpc protocol which called this function.
'''
func.__pass_rpc__ = True
return func
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment