view openwebchat.py @ 37:979b247cba5d

Serialization of conversation is now done in JSON format.
author Atul Varma <varmaa@toolness.com>
date Mon, 27 Apr 2009 16:56:46 -0700
parents d93c08dca64b
children d6467f3845ad
line wrap: on
line source

import os
import math
import re
import httplib
import cStringIO
import mimetools

from cosocket import *
import channels

try:
    import json
except ImportError:
    import simplejson as json

class Conversation(list):
    def __init__(self, fileobj):
        list.__init__(self)
        self.__file = fileobj
        items = []
        for line in self.__file.readlines():
            items.append(json.loads(line))
        self[:] = items

    def append(self, item):
        list.append(self, item)
        self.__file.write('%s\n' % json.dumps(item))
        self.__file.flush()

class OpenWebChatServer(object):
    CONVERSATION_URL = re.compile('\/conversations\/([A-Za-z0-9_]+)')

    BOUNDARY = "'''"

    BLOCK_SIZE = 8192

    MIME_TYPES = {'html' : 'text/html',
                  'js' : 'text/javascript',
                  'css' : 'text/css'}

    def __init__(self, addr, conversation):
        self._conv = conversation
        self._server = CoroutineSocketServer(addr,
                                             self._server_coroutine)

    def run(self):
        self._server.run()

    def _until_http_response_sent(self, msg = '', mimetype = 'text/plain',
                                  length = None, code = 200):
        headers = {'Keep-Alive': 'timeout=99, max=99',
                   'Connection': 'Keep-Alive',
                   'Content-Type': mimetype}
        if not mimetype.startswith('multipart'):
            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 until_sent(content)

    def _until_file_sent(self, filename):
        mimetype = self.MIME_TYPES[filename.split('.')[-1]]

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

        yield self._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(self.BLOCK_SIZE)
            yield until_sent(block)

    def _server_coroutine(self, addr):
        while 1:
            yield self._until_one_request_processed(addr)

    def _until_multipart_header_sent(self, boundary):
        yield self._until_http_response_sent(
            '--%s\r\n' % boundary,
            mimetype = ('multipart/x-mixed-replace; '
                        'boundary="%s"' % boundary))

    def _until_multipart_part_sent(self, boundary, msg):
        yield until_sent('\r\n'.join(
                ('Content-Length: %d' % len(msg),
                 'Content-Type: text/plain',
                 '',
                 msg,
                 '',
                 '--%s' % boundary,
                 '')))

    def _until_one_request_processed(self, addr):
        request = yield until_received(terminator = '\r\n\r\n')
        request = request.splitlines()
        request_line = request[0]
        stringfile = cStringIO.StringIO('\n'.join(request[1:]))
        headers = mimetools.Message(stringfile)
        req_parts = request_line.split()
        if req_parts[1] == '/listen':
            yield self._until_multipart_header_sent(self.BOUNDARY)
            i = 0
            while 1:
                while i < len(self._conv):
                    msg = json.dumps(self._conv[i])
                    i += 1
                    yield self._until_multipart_part_sent(self.BOUNDARY, msg)
                yield channels.until_message_received('new-message')
        elif req_parts[1] == '/send':
            length = int(headers.getheader('Content-Length', 0))
            msg = yield until_received(bytes = length)
            self._conv.append(json.loads(msg))
            yield channels.until_message_sent('new-message', None)
            yield self._until_http_response_sent('sent.')
        elif req_parts[1] in ['/', '/jquery.js', '/openwebchat.js',
                              '/json2.js', '/openwebchat.css']:
            if req_parts[1] == '/':
                filename = 'openwebchat.html'
            else:
                filename = req_parts[1][1:]

            yield self._until_file_sent(filename)
        else:
            match = self.CONVERSATION_URL.match(req_parts[1])
            if match:
                yield self._until_http_response_sent('not implemented',
                                                     code = 501)
            else:
                yield self._until_http_response_sent('not found',
                                                     code = 404)

if __name__ == '__main__':
    CONVERSATION_FILE = 'conversation.dat'

    if not os.path.exists(CONVERSATION_FILE):
        open(CONVERSATION_FILE, 'w').close()

    server = OpenWebChatServer(('127.0.0.1', 8071),
                               Conversation(open(CONVERSATION_FILE, 'r+w')))
    server.run()