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()