Sun, 24 Sep 2017 19:27:30 +0200
Extended the JsonServer to handle multiple clients simultaneously.
# -*- coding: utf-8 -*- # Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the JSON based client base class. """ from __future__ import unicode_literals try: bytes = unicode import StringIO as io # __IGNORE_EXCEPTION__ except NameError: import io # __IGNORE_WARNING__ import sys import socket import select import traceback import json class JsonClient(object): """ Class implementing the JSON based client base class. """ def __init__(self, host, port, idString=""): """ Constructor @param host ip address the background service is listening @type str @param port port of the background service @type int @param idString assigned client id to be sent back to the server in order to identify the connection @param str """ self.__exitClient = False self.__connection = socket.create_connection((host, port)) if idString: reply = idString + '\n' self.__connection.sendall(reply.encode('utf8', 'backslashreplace')) def sendJson(self, command, params): """ Public method to send a single refactoring command to the server. @param command command name to be sent @type str @param params dictionary of named parameters for the command @type dict """ commandDict = { "jsonrpc": "2.0", "method": command, "params": params, } cmd = json.dumps(commandDict) + '\n' self.__connection.sendall(cmd.encode('utf8', 'backslashreplace')) def __receiveJson(self): """ Private method to receive a JSON encode command and data from the server. @return tuple containing the received command and a dictionary containing the associated data @rtype tuple of (str, dict) """ line = self.__connection.recv(1024 * 1024, socket.MSG_PEEK) # 1MB buffer eol = line.find(b'\n') if eol >= 0: size = eol + 1 # Now we know how big the line is, read it for real. line = self.__connection.recv(size).decode( 'utf8', 'backslashreplace') try: commandDict = json.loads(line.strip()) except (TypeError, ValueError) as err: self.sendJson("ClientException", { "ExceptionType": "ProtocolError", "ExceptionValue": str(err), "ProtocolData": line.strip(), }) return None, None method = commandDict["method"] params = commandDict["params"] return method, params return None, None def handleCall(self, method, params): """ Public method to handle a method call from the server. Note: This is an empty implementation that must be overridden in derived classes. @param method requested method name @type str @param params dictionary with method specific parameters @type dict """ pass def run(self): """ Public method implementing the main loop of the client. """ try: while True: try: rrdy, wrdy, xrdy = select.select( [self.__connection], [], []) except (select.error, KeyboardInterrupt, socket.error): # just carry on continue if self.__connection in rrdy: method, params = self.__receiveJson() if method is not None: if method == "Exit": self.__exitClient = True else: self.handleCall(method, params) if self.__exitClient: break except Exception: exctype, excval, exctb = sys.exc_info() tbinfofile = io.StringIO() traceback.print_tb(exctb, None, tbinfofile) tbinfofile.seek(0) tbinfo = tbinfofile.read() del exctb self.sendJson("ClientException", { "ExceptionType": str(exctype), "ExceptionValue": str(excval), "Traceback": tbinfo, }) # Give time to process latest response on server side self.__connection.shutdown(socket.SHUT_RDWR) self.__connection.close() def poll(self, waitMethod=""): """ Public method to check and receive one message (if available). @param waitMethod name of a method to wait for @type str @type bool @return dictionary containing the data of the waited for method @rtype dict """ try: if waitMethod: rrdy, wrdy, xrdy = select.select( [self.__connection], [], []) else: rrdy, wrdy, xrdy = select.select( [self.__connection], [], [], 0) if self.__connection in rrdy: method, params = self.__receiveJson() if method is not None: if method == "Exit": self.__exitClient = True elif method == waitMethod: return params else: self.handleCall(method, params) except (select.error, KeyboardInterrupt, socket.error): # just ignore these pass except Exception: exctype, excval, exctb = sys.exc_info() tbinfofile = io.StringIO() traceback.print_tb(exctb, None, tbinfofile) tbinfofile.seek(0) tbinfo = tbinfofile.read() del exctb self.sendJson("ClientException", { "ExceptionType": str(exctype), "ExceptionValue": str(excval), "Traceback": tbinfo, }) return None