Welcome, guest | Sign In | My Account | Store | Cart
"""A generic, multi-protocol asynchronous server

Usage :
- create a server on a specific host and port : server = Server(host,port)
- call the loop() function, passing it the server and the class used to 
manage the protocol (a subclass of ClientHandler) : loop(server,ProtocolClass)

An example of protocol class is provided, LengthSepBody : the client sends
the message length, the line feed character and the message body
"""

import cStringIO
import socket
import select

# the dictionary holding one client handler for each connected client
# key = client socket, value = instance of (a subclass of) ClientHandler
client_handlers = {}

# =======================================================================
# The server class. Creating an instance starts a server on the specified
# host and port
# =======================================================================
class Server:

    def __init__(self,host='localhost',port=80):
        self.host,self.port = host,port
        self.socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        self.socket.setblocking(0)
        self.socket.bind((host,port))
        self.socket.listen(5)

# =====================================================================
# Generic client handler. An instance of this class is created for each
# request sent by a client to the server
# =====================================================================
class ClientHandler:

    blocksize = 2048

    def __init__(self, server, client_socket, client_address):
        self.server = server
        self.client_address = client_address
        self.client_socket = client_socket
        self.client_socket.setblocking(0)
        self.host = socket.getfqdn(client_address[0])
        self.incoming = '' # receives incoming data
        self.writable = False
        self.close_when_done = True
 
    def handle_error(self):
        self.close()
        
    def handle_read(self):
        """Reads the data received"""
        try:
            buff = self.client_socket.recv(1024)
            if not buff:  # the connection is closed
                self.close()
            # buffer the data in self.incoming
            self.incoming += buff #.write(buff)
            self.process_incoming()
        except socket.error:
            self.close()

    def process_incoming(self):
        """Test if request is complete ; if so, build the response
        and set self.writable to True"""
        if not self.request_complete():
            return
        self.response = self.make_response()
        self.writable = True

    def request_complete(self):
        """Return True if the request is complete, False otherwise
        Override this method in subclasses"""
        return True
    
    def make_response(self):
        """Return the list of strings or file objects whose content will
        be sent to the client
        Override this method in subclasses"""
        return ["xxx"]

    def handle_write(self):
        """Send (a part of) the response on the socket
        Finish the request if the whole response has been sent
        self.response is a list of strings or file objects
        """
        # get next piece of data from self.response
        buff = ''
        while self.response and not buff:
            if isinstance(self.response[0],str):
                buff = self.response.pop(0)
            else:
                buff = self.response[0].read(self.blocksize)
                if not buff:
                    self.response.pop(0)
        if buff:
            try:
                self.client_socket.sendall(buff)
            except socket.error:
                self.close()
            if self.response:
                return
        # nothing left in self.response
        if self.close_when_done:
            self.close() # close socket
        else:
            # reset for next request
            self.writable = False
            self.incoming = ''
    
    def close(self):
        del client_handlers[self.client_socket]
        self.client_socket.close()

# ==============================================================
# A protocol with message length + line feed (\n) + message body
# This implementation just echoes the message body
# ==============================================================
class LengthSepBody(ClientHandler):

    def request_complete(self):
        """The request is complete if the separator is present and the
        number of bytes received equals the specified message length"""
        recv = self.incoming.split('\n',1)
        if len(recv)==1 or len(recv[1]) != int(recv[0]):
            return False
        self.msg_body = recv[1]
        return True

    def make_response(self):
        """Override this method to actually process the data"""
        return [self.msg_body]

# ============================================================================
# Main loop, calling the select() function on the sockets to see if new 
# clients are trying to connect, if some clients have sent data and if those
# for which the response is complete are ready to receive it
# For each event, call the appropriate method of the server or of the instance
# of ClientHandler managing the dialog with the client : handle_read() or 
# handle_write()
# ============================================================================
def loop(server,handler,timeout=30):
    while True:
        k = client_handlers.keys()
        # w = sockets to which there is something to send
        # we must test if we can send data
        w = [ cl for cl in client_handlers if client_handlers[cl].writable ]
        # the heart of the program ! "r" will have the sockets that have sent
        # data, and the server socket if a new client has tried to connect
        r,w,e = select.select(k+[server.socket],w,k,timeout)
        for e_socket in e:
            client_handlers[e_socket].handle_error()
        for r_socket in r:
            if r_socket is server.socket:
                # server socket readable means a new connection request
                try:
                    client_socket,client_address = server.socket.accept()
                    client_handlers[client_socket] = handler(server,
                        client_socket,client_address)
                except socket.error:
                    pass
            else:
                # the client connected on r_socket has sent something
                client_handlers[r_socket].handle_read()
        w = set(w) & set(client_handlers.keys()) # remove deleted sockets
        for w_socket in w:
            client_handlers[w_socket].handle_write()

History