# This is an attempt to do a really simple server in python. import sys, string, socket, select import errno class Selector: '''Selector implements a select() based server model. No initialization arguments. methods: - addreader(reader), addwriter(writer), delreader(reader), delwriter(writer): Add or delete selectable objects for reading or writing. The same object may be added in both categories. Deleting nonexistent objects will raise an error. - isreader(o), iswriter(o): is object o currently a reader (or a writer) - timeout(n): set the select timeout value to n. The default timeout is None. - abort(): cease operations. Most useful when called from insinde loop(). - once(): process the select loop once. - loop(): process the select loop until abort() is called or there is nothing left reading or writing. When an object becomes ready for reading or writing its 'inhandle' or 'outhandle' method is called. If a socket error is raised by this, the object is deleted from the respective list of objects to be selected on. ''' def __init__(self): self._readers = [] self._writers = [] self._timeout = None self._abort = None def addreader(self, reader): self._readers.append(reader) def addwriter(self, writer): self._writers.append(writer) def delreader(self, reader): self._readers.remove(reader) def delwriter(self, writer): self._writers.remove(writer) def isreader(self, o): return o in self._readers def iswriter(self, o): return o in self._writers def isreaders(self): return len(self._readers) > 0 def iswriters(self): return len(self._writers) > 0 def settimeout(self, timeout): self._timeout = timeout def abort(self): self._abort = 1 def once(self): (rdrs, wrtrs, t) = select.select(self._readers, self._writers, [], self._timeout) # If the reader or writer operations encounter an IO # error, we kill the reader (or writer). This keeps things # neat (hopefully), and going. If everyone dies, well... for rdr in rdrs: try: rdr.inhandle() except socket.error: try: self.delreader(rdr) except: pass for wrtr in wrtrs: try: wrtr.outhandle() except socket.error: try: self.delwriter(wrtr) except: pass def loop(self): while not self._abort and (len(self._readers) > 0 or len(self._writers) > 0): self.once() # This is a superclass, not intended to be used directly. class Socket: '''The superclass for sockets for use with Selector. Socket is not directly usable; it exists to be a class parent. Initialization arguments: Socket(socket, selector-obj, [option = ..., option =...]) 'socket' is the underlying socket, and selector-obj is the selector object we are to be associated with. Common Socket methods: - fileno(): supplies the socket file number for select. - setopts(option = ..., [...]): standard way to set object options by name. - abort(): abruptly and gracelessly terminates the socket connection on error. - shutdown(): gracefully terminates socket connection on normal shutdown. ''' def fileno(self): return self._socket.fileno() def getpeername(self): return self._socket.getpeername() def getsockname(self): return self._socket.getsockname() def setopts(self, **kwargs): for i in kwargs.keys(): if hasattr(self, i): setattr(self, i, kwargs[i]) else: raise TypeError, 'option %s does not exist' % (i,) # This is the class for listening (selector-accepting) sockets. class ListenSocket(Socket): '''ListenSocket implements a listening socket. Init parameters: listenbacklog = some value Important methods: - setchildargs(CreateFN, keyword = a, keyword2 = b, ...): Sets the creation function for this object. The creation function is called when a new connection is accepted, and is called as 'CreateFN(socket, selector-obj, addr = addr, listener = ME)'. It is expected to return a selectable-for-read object that will be added to the selector. Traditionally the creation function is a class, eg DataSocket. If optional keyword arguments are supplied, they will be hoovered up and sent to newobj.setopts(). A ListenSocket with no creation function will simply accept and then discard new connection sockets. ''' childinit = None childopts = {} listenbacklog = 10 def __init__(self, socket, selector, **args): self._socket = socket self.selector = selector apply(self.setopts, (), args) self._socket.setblocking(0) self._socket.listen(self.listenbacklog) def abort(self): if self.selector: self.selector.delreader(self) self.selector = None self._socket.close() self._socket = None def shutdown(self): self.abort() def setchildargs(self, spawner, **kwopts): self.childinit = spawner self.childopts = kwopts def inhandle(self): (sock, addr) = self._socket.accept() sock.setblocking(0) # We apply some black magic with a keyword dictionary to # make this thing go. if self.childinit: res = self.childinit(sock, self.selector, addr = addr, listener = self) if res: if self.childopts: apply(res.setopts, (), self.childopts) self.selector.addreader(res) else: # Bye bye. socket.close() class DataSocket(Socket): '''DataSocket accepts block data with a maximum buffer size. DataSocket is a Socket subclass. Initailization arguments: socket, selector-obj, optional options as keyword arguments. Available options: - maxbufsize: constrain the amount of data we buffer without being able to process. We will never buffer more than this amount, and if we have this amount buffered and cannot process it it is an error and we abort ourselves. Default: unlimited. - blocksize: if less than maxbufsize, we read data from the network in chunks no larger than this. - listener, addr: the listening socket that created us and the address of the remote endpoint of our socket, respectively. (Set at creation.) - handlerinit: the function called to create a handler object for data read from the socket. See setchildargs(). Methods: - setchildargs(CreateFN[, option = val, ...]): CreateFN is called to create a handler object, with the optional keyword arguments. A handler object has two methods: processin(str) and errormax(). The former is called to process a block of data and must return the number of bytes of the block it has consumed (and not touch the block); it may return 0 if it can do nothing right now. The latter is called when the buffer overflows and the socket is about to be aborted. Conventionally the creation function is a class. The creation function is called with one normal argument, the DataSocket object it is associated with. - send(str): send the string out the socket network connection. If there is no creation function, data read is quietly discarded. The processin() method is called every time we have read a block of data from the network. It is called with all unconsumed data to date (both newly read and any old data not yet consumed). If it has received incomplete data, it should return 0 and wait for more to show up. ''' listener = handlerinit = addr = None handleropts = {} maxbufsize = sys.maxint blocksize = 16 * 1024 def __init__(self, socket, selector, **kwargs): self._socket = socket self.selector = selector self._inbuf = self._outbuf = '' self._live = 1 self._handler = None apply(self.setopts, (), kwargs) def setchildargs(self, spawner, **kwopts): self.handlerinit = spawner self.handleropts = kwopts def _inithandler(self): if self._handler: return self._handler if not self.handlerinit: return None self._handler = apply(self.handlerinit, (self,), self.handleropts) return self._handler def abort(self): if not self._socket: return self._socket.close() if self._outbuf and self.selector: self.selector.delwriter(self) if self._live: self.selector.delreader(self) self._live = 0 self._socket = None self.selector = None def shutdown(self): '''Gracefully shut down reading and clean up.''' if not self._live or not self._socket: return self._live = 0 self.selector.delreader(self) if not self._outbuf: self._socket.close() self._socket = None else: self._socket.shutdown(0) # Our write operation will clean itself up. def outhandle(self): res = self._socket.send(self._outbuf) self._outbuf = self._outbuf[res:] if len(self._outbuf) == 0: self.selector.delwriter(self) # Are we shutting down? if not self._live: self._socket.close() self._socket = None def send(self, str): kick = 1 if not self._outbuf: self.selector.addwriter(self) else: # cannot kick with a pending buffer, we might have # exhausted our non-blocking write buffer. kick = 0 self._outbuf = self._outbuf + str # We can get processed in the select loop, but kick it here: if kick: self.outhandle() def processin(self): if not self._inithandler(): # With no action handler, just eat all the data. self._inbuf = '' return while len(self._inbuf) > 0 and self._live: res = self._handler.processin(self._inbuf) if res == 0: if len(self._inbuf) == self.maxbufsize: self._handler.errormax() self.abort() return else: self._inbuf = self._inbuf[res:] def inhandle(self): self._inithandler() t = None work = 0 # Why loop? Because we need to reblock the input if necessary. # While we may permit a large input size, it doesn't mean we # want to create a buffer that big on each pass! while len(self._inbuf) < self.maxbufsize: chunk = self.maxbufsize - len(self._inbuf) if chunk > self.blocksize: chunk = self.blocksize # socket must be nonblocking! try: t = self._socket.recv(chunk) except socket.error, (err, msg): if err == errno.EAGAIN: break else: raise socket.error, (err, msg) if not t: break self._inbuf = self._inbuf + t work = 1 # we always process here even on null reads so that we can # attempt to flush any pending data through processing at EOF self.processin() if not work: # We have hit EOF. Shutdown or close the socket. self.shutdown() class LineDataSocket(DataSocket): '''LineDataSocket accepts and processes input a newline-terminated line at a time. LineDataSocket is a subclass of DataSocket. Unlike DataSocket, LineDataSocket calls its handler's processin method once for each completed newline-terminated line it receives, passing the processin method the line sans terminating newline. It ignores the return value; lines passed to the handler are discarded and will not be reproccessed. It defines one new method: - pending(): returns true if there is more data read from the network and pending. ''' def pending(self): return not len(self._inbuf) == 0 def processin(self): if not self._inithandler(): # ungh. Might as well eat pending partial lines too. self._inbuf = '' return while len(self._inbuf) > 0 and self._live: pos = string.find(self._inbuf, '\n') if pos == -1: if len(self._inbuf) >= self.maxbufsize: self._handler.errormax() self.abort() return line = self._inbuf[:pos] # unlike the normal case, we skip the \n. self._inbuf = self._inbuf[pos+1:] self._handler.processin(line) # This is a base class for socket handlers: class BaseSocketHandler: '''BaseSocketHandler is the base class of Socket handler classes. BaseSocketHandler defines the necessary behavior for a 'null' handler class for DataSocket and descendants. A BaseSocketHandler object simply consumes all data handed to it silently. ''' def __init__(self, sobj): self.sock = sobj def errormax(self): pass def processin(self, str): return len(str)