# ATTRIBUTION: Pierre Quentel
# http://code.activestate.com/recipes/511453/
# LICENSE: MIT
# http://code.activestate.com/help/terms/
# http://www.opensource.org/licenses/mit-license.php
"""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.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
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()