durusmail: quixote-users: Re: async HTTP server included?
async HTTP server included?
2004-01-06
2004-01-06
Re: async HTTP server included?
2004-01-06
2004-01-06
async HTTP server included?
2004-01-06
Re: async HTTP server included?
2004-01-06
async HTTP server included?
2004-01-07
Re: async HTTP server included?
2004-01-07
2004-01-07
2004-01-07
2004-01-07
Re: Licensing
2004-01-07
2004-01-07
2004-01-07
Pipelining the async HTTP server
2004-01-07
Re: Pipelining the async HTTP server
2004-01-07
2004-01-07
2004-01-08
Re: Pipelining the async HTTP server
2004-01-08
2004-01-08
2004-01-08
2004-01-08
quixote.server.medusa (Re: Pipelining the async HTTP server)
2004-01-08
quixote.server.medusa
2004-01-08
2004-01-12
Re: quixote.server.medusa (Re: Pipelining the async HTTP server)
2004-01-13
Problem with using quixote.server.medusa vs. standalone medusa
2004-01-14
Re: Problem with using quixote.server.medusa vs. standalone medusa
2004-01-14
Resolved! Was Re: [Quixote-users] Re: Problem with using quixote.server.medusa vs. standalone medusa
2004-01-14
Re: Resolved! Was Re: Re: Problem with using quixote.server.medusa vs. standalone medusa
2004-01-14
Pipelining the async HTTP server
2004-01-08
2004-01-08
Re: Pipelining the async HTTP server
2004-01-08
2004-01-08
2004-01-06
Re: async HTTP server included?
2004-01-06
Re: async HTTP server included?
Graham Fawcett
2004-01-06
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()
reply