The asynchat Module
The asynchat module is an extension to asyncore. It provides additional support for line-oriented protocols. It also provides improved buffering support, via the push methods and the "producer" mechanism.
Example 7-12 implements a very minimal HTTP responder. It simply returns an HTML document containing information from an HTTP request (the output appears in the browser window).
Example 7-12. Using the asynchat Module to Implement a Minimal HTTP Server
File: asynchat-example-1.py import asyncore, asynchat import os, socket, string PORT = 8000 class HTTPChannel(asynchat.async_chat): def _ _init_ _(self, server, sock, addr): asynchat.async_chat._ _init_ _(self, sock) self.set_terminator(" ") self.request = None self.data = "" self.shutdown = 0 def collect_incoming_data(self, data): self.data = self.data + data def found_terminator(self): if not self.request: # got the request line self.request = string.split(self.data, None, 2) if len(self.request) != 3: self.shutdown = 1 else: self.push("HTTP/1.0 200 OK ") self.push("Content-type: text/html ") self.push(" ") self.data = self.data + " " self.set_terminator(" ") # look for end of headers else: # return payload. self.push("
") self.push(self.data) self.push("
") self.close_when_done() class HTTPServer(asyncore.dispatcher): def _ _init_ _(self, port): self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.bind(("", port)) self.listen(5) def handle_accept(self): conn, addr = self.accept() HTTPChannel(self, conn, addr) # # try it out s = HTTPServer(PORT) print "serving at port", PORT, "..." asyncore.loop() GET / HTTP/1.1 Accept: */* Accept-Language: en, sv Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; Bruce/1.0) Host: localhost:8000 Connection: Keep-Alive
The producer interface allows you to "push" objects that are too large to store in memory. asyncore calls the producer's more method whenever it needs more data. To signal end of file, just return an empty string.
Example 7-13 implements a very simple file-based HTTP server, using a simple FileProducer class that reads data from a file, a few kilobytes at the time.
Example 7-13. Using the asynchat Module to Implement a Simple HTTP Server
File: asynchat-example-2.py import asyncore, asynchat import os, socket, string, sys import StringIO, mimetools ROOT = "." PORT = 8000 class HTTPChannel(asynchat.async_chat): def _ _init_ _(self, server, sock, addr): asynchat.async_chat._ _init_ _(self, sock) self.server = server self.set_terminator(" ") self.header = None self.data = "" self.shutdown = 0 def collect_incoming_data(self, data): self.data = self.data + data if len(self.data) > 16384: # limit the header size to prevent attacks self.shutdown = 1 def found_terminator(self): if not self.header: # parse http header fp = StringIO.StringIO(self.data) request = string.split(fp.readline(), None, 2) if len(request) != 3: # badly formed request; just shut down self.shutdown = 1 else: # parse message header self.header = mimetools.Message(fp) self.set_terminator(" ") self.server.handle_request( self, request[0], request[1], self.header ) self.close_when_done() self.data = "" else: pass # ignore body data, for now def pushstatus(self, status, explanation="OK"): self.push("HTTP/1.0 %d %s " % (status, explanation)) class FileProducer: # a producer that reads data from a file object def _ _init_ _(self, file): self.file = file def more(self): if self.file: data = self.file.read(2048) if data: return data self.file = None return "" class HTTPServer(asyncore.dispatcher): def _ _init_ _(self, port=None, request=None): if not port: port = 80 self.port = port if request: self.handle_request = request # external request handler self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.bind(("", port)) self.listen(5) def handle_accept(self): conn, addr = self.accept() HTTPChannel(self, conn, addr) def handle_request(self, channel, method, path, header): try: # this is not safe! while path[:1] == "/": path = path[1:] filename = os.path.join(ROOT, path) print path, "=>", filename file = open(filename, "r") except IOError: channel.pushstatus(404, "Not found") channel.push("Content-type: text/html ") channel.push(" ") channel.push("
File not found. ") else: channel.pushstatus(200, "OK") channel.push("Content-type: text/html ") channel.push(" ") channel.push_with_producer(FileProducer(file)) # # try it out s = HTTPServer(PORT) print "serving at port", PORT asyncore.loop() serving at port 8000 log: adding channel log: adding channel samples/sample.htm => .samples/sample.htm log: closing channel 96: