While at Immersive Labs, Alessio Signorini and I frequently found ourselves having to write custom mini-servers for interactive content. To help minimize the differences between the custom content we devised two super simple object oriented JSON client and server objects. Using this module you can easily create a custom JSON messaging server by overloading just one method. The latest code plus examples can be found on GitHub here. In addition, official releases can be found on PyPi and installed using “easy_install jsocket”.
The JsonSocket class below is the base class for both the client and server objects. Its responsible for handling all the socket level send/receive operations. Each message contains two parts, a header and a payload. The header is nothing more then the length of the payload and the payload is just a JSON object.
#git tag: v1.1-pypi
""" contains a json object message passing server and client """
import json
import socket
import struct
import logging
logger = logging.getLogger("jsonSocket")
logger.setLevel(logging.DEBUG)
FORMAT = '[%(asctime)-15s][%(levelname)s][%(funcName)s] %(message)s'
logging.basicConfig(format=FORMAT)
class JsonSocket(object):
def __init__(self, address='127.0.0.1', port=5489):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.conn = self.socket
self._timeout = None
self._address = address
self._port = port
def sendObj(self, obj):
msg = json.dumps(obj)
if self.socket:
frmt = "=%ds" % len(msg)
packedMsg = struct.pack(frmt, msg)
packedHdr = struct.pack('=I', len(packedMsg))
self._send(packedHdr)
self._send(packedMsg)
def _send(self, msg):
sent = 0
while sent < len(msg):
sent += self.conn.send(msg[sent:])
def _read(self, size):
data = ''
while len(data) < size:
dataTmp = self.conn.recv(size-len(data))
data += dataTmp
if dataTmp == '':
raise RuntimeError("socket connection broken")
return data
def _msgLength(self):
d = self._read(4)
s = struct.unpack('=I', d)
return s[0]
def readObj(self):
size = self._msgLength()
data = self._read(size)
frmt = "=%ds" % size
msg = struct.unpack(frmt,data)
return json.loads(msg[0])
def close(self):
logger.debug("closing main socket")
self._closeSocket()
if self.socket is not self.conn:
logger.debug("closing connection socket")
self._closeConnection()
def _closeSocket(self):
self.socket.close()
def _closeConnection(self):
self.conn.close()
def _get_timeout(self):
return self._timeout
def _set_timeout(self, timeout):
self._timeout = timeout
self.socket.settimeout(timeout)
def _get_address(self):
return self._address
def _set_address(self, address):
pass
def _get_port(self):
return self._port
def _set_port(self, port):
pass
timeout = property(_get_timeout, _set_timeout,doc='Get/set the socket timeout')
address = property(_get_address, _set_address,doc='read only property socket address')
port = property(_get_port, _set_port,doc='read only property socket port')
The JsonServer class defined below inherits the above JsonSocket and makes available many of the standard server operations such as listen, bind and accept. You can invoke this class directly if you want to add a JsonServer to an already existing thread. However, the preferred method is to inherit the ThreadedServer class described below.
#git tag: v1.1-pypi
class JsonServer(JsonSocket):
def __init__(self, address='127.0.0.1', port=5489):
super(JsonServer, self).__init__(address, port)
self._bind()
def _bind(self):
self.socket.bind( (self.address,self.port) )
def _listen(self):
self.socket.listen(1)
def _accept(self):
return self.socket.accept()
def acceptConnection(self):
self._listen()
self.conn, addr = self._accept()
self.conn.settimeout(self.timeout)
logger.debug("connection accepted, conn socket (%s,%d)" % (addr[0],addr[1]))
Just like the JsonServer class above, the JsonClient also inherits the JsonSocket base class. For simplicity, and thanks to the functionality encapsulation of JsonSocket, the client only needs to support one connect method.
#git tag: v1.1-pypi
class JsonClient(JsonSocket):
def __init__(self, address='127.0.0.1', port=5489):
super(JsonClient, self).__init__(address, port)
def connect(self):
for i in range(10):
try:
self.socket.connect( (self.address, self.port) )
except socket.error as msg:
logger.error("SockThread Error: %s" % msg)
time.sleep(3)
continue
logger.info("...Socket Connected")
return True
return False
In order for the JsonServer to be useful a thread must be attached to it. The following ThreadedServer class inherits both JsonSocket and Python’s thread class. By inheriting both of these classes we can combine all of the server functionality into one class, making it unnecessary to change any of the underlying message passing functionality. Currently this implementation accepts only one connection per thread. Notice that the “private” _processMessage method must be implemented by another inheriting class.
#git tag: v1.1-pypi
""" A threaded server based on a JsonServer object in the jsonSocket module """
import jsonSocket
import threading
import socket
import logging
logger = logging.getLogger("threadedServer")
logger.setLevel(logging.DEBUG)
FORMAT = '[%(asctime)-15s][%(levelname)s][%(funcName)s] %(message)s'
logging.basicConfig(format=FORMAT)
class ThreadedServer(threading.Thread, jsonSocket.JsonServer):
def __init__(self, **kwargs):
threading.Thread.__init__(self)
jsonSocket.JsonServer.__init__(self)
self._isAlive = False
def _processMessage(self, obj):
""" virtual method """
pass
def run(self):
while self._isAlive:
try:
self.acceptConnection()
except socket.timeout as e:
logger.debug("socket.timeout: %s" % e)
continue
except Exception as e:
logger.exception(e)
continue
while self._isAlive:
try:
obj = self.readObj()
self._processMessage(obj)
except socket.timeout as e:
logger.debug("socket.timeout: %s" % e)
continue
except Exception as e:
logger.exception(e)
self._closeConnection()
break
def start(self):
self._isAlive = True
super(ThreadedServer, self).start()
def stop(self):
""" The life of the dead is in the memory of the living """
self._isAlive = False
Finally, devising a custom server is as simple as creating a new class which inherits ThreadedServer and implementing the _processMessage(obj) method. To implement a client just instantiate a JsonClient object, execute connect(), and pass messages back and forth using send/readObj(obj).
#git tag: v1.1-pypi
import jsonSocket
class MyServer(jsonSocket.ThreadedServer):
def __init__(self):
super(MyServer, self).__init__()
self.timeout = 2.0
def _processMessage(self, obj):
""" virtual method """
if obj != '':
if obj['message'] == "new connection":
pass
if __name__ == "__main__":
c = MyServer()
c.start()
Pingback: Python JSON Client & Server Redux | Christopher Piekarski