import socket import threading from platypush.backend import Backend from platypush.message import Message from platypush.utils import set_thread_name class TcpBackend(Backend): """ Backend that reads messages from a configured TCP port """ # Maximum length of a request to be processed _MAX_REQ_SIZE = 2048 def __init__(self, port, bind_address=None, listen_queue=5, *args, **kwargs): """ :param port: TCP port number :type port: int :param bind_address: Specify a bind address if you want to hook the service to a specific interface (default: listen for any connections) :type bind_address: str :param listen_queue: Maximum number of queued connections (default: 5) :type listen_queue: int """ super().__init__(*args, **kwargs) self.port = port self.bind_address = bind_address or '0.0.0.0' self.listen_queue = listen_queue def _process_client(self, sock, address): def _f(): processed_bytes = 0 open_brackets = 0 msg = b'' prev_ch = None while not self.should_stop(): if processed_bytes > self._MAX_REQ_SIZE: self.logger.warning('Ignoring message longer than {} bytes from {}' .format(self._MAX_REQ_SIZE, address[0])) return ch = sock.recv(1) processed_bytes += 1 if ch == b'': break if ch == b'{' and prev_ch != b'\\': open_brackets += 1 if not open_brackets: continue msg += ch if ch == b'}' and prev_ch != b'\\': open_brackets -= 1 if not open_brackets: break prev_ch = ch if msg == b'': return msg = Message.build(msg) self.logger.info('Received request from {}: {}'.format(msg, address[0])) self.on_message(msg) response = self.get_message_response(msg) self.logger.info('Processing response on the TCP backend: {}'.format(response)) if response: sock.send(str(response).encode()) def _f_wrapper(): set_thread_name('TCPListener') try: _f() finally: sock.close() threading.Thread(target=_f_wrapper, name='TCPListener').start() def run(self): super().run() self.register_service(port=self.port) serv_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serv_sock.bind((self.bind_address, self.port)) serv_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) serv_sock.settimeout(0.5) self.logger.info('Initialized TCP backend on port {} with bind address {}'. format(self.port, self.bind_address)) serv_sock.listen(self.listen_queue) while not self.should_stop(): try: (sock, address) = serv_sock.accept() except socket.timeout: continue self.logger.info('Accepted connection from client {}'.format(address[0])) self._process_client(sock, address) self.logger.info('TCP backend terminated') # vim:sw=4:ts=4:et: