Super Easy Python JSON Client & Server

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()
About these ads

One thought on “Super Easy Python JSON Client & Server

  1. Pingback: Python JSON Client & Server Redux | Christopher Piekarski

Comments are closed.