Mercurial > cosocket
view openwebchat.py @ 63:b19641a0d5ad
Refactoring: better querystring parsing.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Wed, 29 Apr 2009 16:26:21 -0700 |
parents | c1b46b60e838 |
children | 559c48a58254 |
line wrap: on
line source
import os import sys import math import re import httplib import cStringIO import mimetools import weakref import cgi from cosocket import * import channels try: import json except ImportError: import simplejson as json class Conversations(object): def __init__(self): self._convs = weakref.WeakValueDictionary() def __len__(self): return len(self._convs) def get(self, name): if name not in self._convs: filename = '%s.conversation' % name if not os.path.exists(filename): open(filename, 'w').close() conv = Conversation(open(filename, 'r+w')) self._convs[name] = conv else: conv = self._convs[name] return conv 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() def _parse_qs(querystring): querydict = {} cgi_querydict = cgi.parse_qs(querystring) for key, value in cgi_querydict.items(): querydict[key] = cgi_querydict[key][0] return querydict class OpenWebChatServer(object): QUERYSTRING_TEMPLATE = re.compile('([^\?]*)\?(.*)') REDIRECT_TEMPLATE = re.compile('\/([A-Za-z0-9_]+)') URL_TEMPLATE = re.compile('\/([A-Za-z0-9_]+)/(.*)') BOUNDARY = "'''" BLOCK_SIZE = 8192 MIME_TYPES = {'html' : 'text/html', 'js' : 'text/javascript', 'css' : 'text/css'} def __init__(self, addr, conversations): self._num_connections = 0 self._convs = conversations 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, additional_headers = None): headers = {'Keep-Alive': 'timeout=99, max=99', 'Connection': 'Keep-Alive', 'Content-Type': mimetype} if additional_headers: headers.update(additional_headers) 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): self._num_connections += 1 try: while 1: yield self._until_one_request_processed(addr) finally: self._num_connections -= 1 def __multipart_boundary(self, boundary, mimetype = 'application/json'): # Here we actually declare the content type and start the # transfer of the document itself; this is needed to # trigger an error on the browser-side, because a closed # connection during any other phase is unlikely to # cause any event to trigger on the client webpage. return '\r\n'.join(('--%s' % boundary, 'Content-Type: %s' % mimetype, '', '')) def _until_multipart_header_sent(self, boundary): yield self._until_http_response_sent( self.__multipart_boundary(boundary), mimetype = ('multipart/x-mixed-replace; ' 'boundary="%s"' % boundary)) def _until_multipart_part_sent(self, boundary, msg): yield until_sent('\r\n'.join( (msg, '', self.__multipart_boundary(boundary)) )) def _parse_id(self, string): i = 0 if string: try: i = int(string) if i < 0: i = 0 except ValueError: pass return i def _until_conv_request_processed(self, addr, headers, method, conv_name, page): match = self.QUERYSTRING_TEMPLATE.match(page) querydict = {} if match: querydict.update(_parse_qs(match.group(2))) page = match.group(1) if page == 'listen/multipart': i = self._parse_id(querydict.get('start')) yield self._until_multipart_header_sent(self.BOUNDARY) conv = self._convs.get(conv_name) while 1: while i < len(conv): msg = json.dumps(conv[i]) i += 1 yield self._until_multipart_part_sent(self.BOUNDARY, msg) yield channels.until_message_received(conv_name) elif page == 'send': length = int(headers.getheader('Content-Length', 0)) msg = yield until_received(bytes = length) conv = self._convs.get(conv_name) conv.append(json.loads(msg)) yield channels.until_message_sent(conv_name, None) yield self._until_http_response_sent('sent.') elif page in ['', 'jquery.js', 'openwebchat.js', 'json2.js', 'openwebchat.css']: if page == '': filename = 'openwebchat.html' else: filename = page yield self._until_file_sent(filename) else: yield self._until_http_response_sent('not found', code = 404) 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() method = req_parts[0] match = self.URL_TEMPLATE.match(req_parts[1]) if not match: match = self.REDIRECT_TEMPLATE.match(req_parts[1]) if match: newpath = req_parts[1] + '/' yield self._until_http_response_sent( newpath, code = 301, additional_headers = {'Location': newpath} ) else: yield self._until_http_response_sent('not found', code = 404) else: conv_name = match.group(1) page = match.group(2) if conv_name == 'status': # TODO: Return 404 if page is non-empty. lines = ('open connections : %d' % self._num_connections, 'open conversations: %d'% len(self._convs)) yield self._until_http_response_sent('\r\n'.join(lines)) else: yield self._until_conv_request_processed(addr, headers, method, conv_name, page) if __name__ == '__main__': if len(sys.argv) > 1: port = int(sys.argv[1]) else: port = 8071 server = OpenWebChatServer(('', port), Conversations()) server.run()