A.M. Kuchling wrote: > On Tue, Jan 06, 2004 at 02:11:38PM -0500, Graham Fawcett wrote: > >>I propose that we adapt and bundle Pierre's code with Quixote as a >>built-in Web server for demo and lightweight use. > Good idea. What's the license on the code? And do you want to work up a > patch, or should I do it? I couldn't resist the urge to put together a patch. Should've been working at my real job instead. Don't tell my boss, he hates when I do that. ;-) Okay, this is really rough; it's a combination of Pierre's code and server.medusa_http. I'm sure there are a few errors in here; but I've managed to get the quixote.demo running on my Win32 workstation. I'll read through this later and try to clean it up a bit. I called it qserver, either for Quixote server or Quentel server, but feel free to adjust the name or anything else. BTW, Pierre replied to my e-mail: > It's very kind of you to ask. I forgot to add a comment in the code to say > it's published under the same licence as the whole Karrigell framework > (GPL). Anyway, feel free to use it and adapt it as you like, and include it > inside Quixote. If you find bugs in it I'd be glad to know as well ! so we are good to go. -- G
""" Simple HTTP server based on the asyncore / asynchat framework. Original code by Pierre Quentel, 2004. See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/259148 Modified for use as a Quixote web handler. This is alpha code; use with care! """ import asynchat,asyncore,socket import os import BaseHTTPServer import cStringIO from quixote import publish from quixote.errors import PublishError from quixote.http_response import Stream class socketStream: def __init__(self,sock): self.sock=sock self.closed=1 # compatibility with SocketServer def write(self,data): self.sock.send(data) class RequestHandler(asynchat.async_chat, BaseHTTPServer.BaseHTTPRequestHandler): server_name = 'Quixote Server' def __init__(self,conn,addr,server): asynchat.async_chat.__init__(self,conn) self.client_address=addr self.connection=conn self.server=server self.wfile=socketStream(self.socket) # sets the terminator : when it is received, this means that the # http request is complete ; control will be passed to # self.found_terminator self.set_terminator ('\r\n\r\n') self.buffer=cStringIO.StringIO() self.found_terminator=self.handle_request_line def handle_data(self): """Class to override""" msg = self.headers if '#' in self.path: # MSIE is buggy and sometimes includes fragments in URLs [self.path, fragment] = self.path.split('#', 1) if '?' in self.path: [path, query_string] = self.path.split('?', 1) else: path = self.path query_string = '' clength = int(msg.get('content-length', '0')) remote_addr, remote_port = self.client_address environ = {'REQUEST_METHOD': self.command, 'ACCEPT_ENCODING': msg.get('Accept-encoding'), 'CONTENT_TYPE': msg.get('Content-type'), 'CONTENT_LENGTH': clength, "GATEWAY_INTERFACE": "CGI/1.1", 'HTTP_COOKIE': msg.get('Cookie'), 'HTTP_REFERER': msg.get('Referer'), 'HTTP_USER_AGENT': msg.get('User-agent'), 'PATH_INFO': path, 'QUERY_STRING': query_string, 'REMOTE_ADDR': remote_addr, 'REMOTE_PORT': str(remote_port), 'REQUEST_URI': path, 'SCRIPT_NAME': '', "SCRIPT_FILENAME": '', 'SERVER_NAME': self.server.ip or socket.gethostname(), 'SERVER_PORT': str(self.server.port), 'SERVER_PROTOCOL': 'HTTP/1.1', 'SERVER_SOFTWARE': self.server_name, } # Propagate HTTP headers - copied from twisted_http.py for title, header in msg.items(): envname = title.replace('-', '_').upper() if title not in ('content-type', 'content-length'): envname = "HTTP_" + envname environ[envname] = header for k,v in environ.items(): if v == None: environ[k] = '' pub = self.server.publisher qreq = pub.create_request(self.rfile, environ) try: pub.parse_request(qreq) output = pub.process_request(qreq, environ) except PublishError, err: output = pub.finish_interrupted_request(qreq, err) except: output = pub.finish_failed_request(qreq) qresponse = qreq.response if output: qresponse.set_body(output) self.send_response(qresponse.status_code) # Copy headers from Quixote's HTTP response for name, value in qresponse.generate_headers(): self.send_header(name, value) self.end_headers() # XXX should we use some asynchronous approach for # stream-based content? if qresponse.body is not None: if isinstance(qresponse.body, Stream): for chunk in qresponse.body: self.wfile.write(chunk) else: self.wfile.write(qresponse.body) def collect_incoming_data(self,data): """Collects the data arriving on the connexion""" self.buffer.write(data) def prepare_POST(self): """Prepare to read the request body""" bytesToRead = int(self.headers.getheader('content-length')) # set terminator to length (will read bytesToRead bytes) self.set_terminator(bytesToRead) self.buffer=cStringIO.StringIO() # control will be passed to a new found_terminator self.found_terminator=self.handle_post_data def handle_post_data(self): """Called when a POST request body has been read""" self.rfile=cStringIO.StringIO(self.buffer.getvalue()) self.do_POST() self.finish() def do_GET(self): """Begins serving a GET request""" # nothing more to do before handle_data() self.handle_data() def do_POST(self): self.handle_data() def handle_request_line(self): """Called when the http request line and headers have been received""" # prepare attributes needed in parse_request() self.rfile=cStringIO.StringIO(self.buffer.getvalue()) self.raw_requestline=self.rfile.readline() self.parse_request() if self.command in ['GET','HEAD']: # if method is GET or HEAD, call do_GET or do_HEAD and finish method="do_"+self.command if hasattr(self,method): getattr(self,method)() self.finish() elif self.command=="POST": # if method is POST, call prepare_POST, don't finish before self.prepare_POST() else: self.send_error(501, "Unsupported method (%s)" %self.command) def finish(self): """Reset terminator (required after POST method), then close""" self.set_terminator ('\r\n\r\n') self.close() class Server(asyncore.dispatcher): """Copied from http_server in medusa""" def __init__ (self, ip, port, publisher, handler=RequestHandler): self.ip = ip self.port = port self.publisher = publisher self.handler=handler asyncore.dispatcher.__init__ (self) self.create_socket (socket.AF_INET, socket.SOCK_STREAM) self.set_reuse_addr() self.bind ((ip, port)) # lower this to 5 if your OS complains self.listen (1024) def handle_accept (self): try: conn, addr = self.accept() except socket.error: self.log_info ('warning: server accept() threw an exception', 'warning') return except TypeError: self.log_info ('warning: server accept() threw EWOULDBLOCK', 'warning') return # creates an instance of the handler class to handle the request/response # on the incoming connexion self.handler(conn,addr,self) if __name__=="__main__": from quixote import enable_ptl enable_ptl() pub = publish.Publisher('quixote.demo') s=Server('', 8080, pub) print "SimpleAsyncHTTPServer running on port 8080" asyncore.loop()