view async_web_server.py @ 37:c6b41464c021 default tip

factored async web server into separate file
author Atul Varma <varmaa@toolness.com>
date Thu, 24 Dec 2009 18:34:51 -0800
parents
children
line wrap: on
line source

import os
import httplib
import cStringIO
import mimetools
import mimetypes
import logging

import cosocket

KEEP_ALIVE_MAX_REQUESTS = 99
KEEP_ALIVE_TIMEOUT = int(cosocket.DEFAULT_TIMEOUT)
KEEP_ALIVE_ENABLED = True

BLOCK_SIZE = 8192

def until_http_response_sent(msg = '', mimetype = 'text/plain',
                             length = None, code = 200,
                             additional_headers = None):
    headers = {'Content-Type': mimetype}
    if KEEP_ALIVE_ENABLED:
        headers.update({'Keep-Alive': 'timeout=%d, max=%d' %
                        (KEEP_ALIVE_TIMEOUT,
                         KEEP_ALIVE_MAX_REQUESTS),
                        'Connection': 'Keep-Alive'})
    if additional_headers:
        headers.update(additional_headers)
    if length is None:
        length = len(msg)
    headers['Content-Length'] = str(length)

    header_lines = ['HTTP/1.1 %d %s' % (code,
                                        httplib.responses[code])]
    header_lines.extend(['%s: %s' % (key, value)
                         for key, value in headers.items()])
    header_lines.extend(['', msg])
    content = '\r\n'.join(header_lines)
    yield cosocket.until_sent(content)

def until_http_file_sent(filename, block_size = BLOCK_SIZE):
    ext = '.' + filename.split('.')[-1]

    if ext in mimetypes.types_map:
        mimetype = mimetypes.types_map[ext]
    else:
        mimetype = 'text/plain'

    length = os.stat(filename).st_size
    num_blocks = length / block_size
    if length % block_size:
        num_blocks += 1
    infile = open(filename, 'r')

    yield until_http_response_sent(mimetype = mimetype,
                                   length = length)

    for i in range(num_blocks):
        # TODO: This could be bad since we're reading the file
        # synchronously.
        block = infile.read(block_size)
        yield cosocket.until_sent(block)

class AsyncWebServer(object):
    def __init__(self, addr, app):
        self._num_connections = 0
        self._app = app
        cosocket.AsyncChatCoroutine(self._server_coroutine(addr))

    def _server_coroutine(self, bind_addr):
        yield cosocket.until_listening(bind_addr)
        while 1:
            conn, addr = yield cosocket.until_connection_accepted()
            cosocket.AsyncChatCoroutine(self._connection_coroutine(addr),
                                        conn)

    def _connection_coroutine(self, addr):
        self._num_connections += 1
        try:
            if KEEP_ALIVE_ENABLED:
                for i in range(KEEP_ALIVE_MAX_REQUESTS):
                    yield self._until_one_request_processed(addr)
            else:
                yield self._until_one_request_processed(addr)
        finally:
            logging.info('Closing connection to %s' % repr(addr))
            self._num_connections -= 1

    def _until_one_request_processed(self, addr):
        request = yield cosocket.until_received(terminator = '\r\n\r\n')
        request = request.splitlines()
        request_line = request[0]
        logging.info("Request from %s: %s" % (addr, request_line))
        stringfile = cStringIO.StringIO('\n'.join(request[1:]))
        headers = mimetools.Message(stringfile)
        req_parts = request_line.split()

        yield self._app.until_request_processed(method = req_parts[0],
                                                path = req_parts[1],
                                                headers = headers,
                                                addr = addr)