Mercurial > bzapi
comparison 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 |
comparison
equal
deleted
inserted
replaced
36:352f4cc55d12 | 37:c6b41464c021 |
---|---|
1 import os | |
2 import httplib | |
3 import cStringIO | |
4 import mimetools | |
5 import mimetypes | |
6 import logging | |
7 | |
8 import cosocket | |
9 | |
10 KEEP_ALIVE_MAX_REQUESTS = 99 | |
11 KEEP_ALIVE_TIMEOUT = int(cosocket.DEFAULT_TIMEOUT) | |
12 KEEP_ALIVE_ENABLED = True | |
13 | |
14 BLOCK_SIZE = 8192 | |
15 | |
16 def until_http_response_sent(msg = '', mimetype = 'text/plain', | |
17 length = None, code = 200, | |
18 additional_headers = None): | |
19 headers = {'Content-Type': mimetype} | |
20 if KEEP_ALIVE_ENABLED: | |
21 headers.update({'Keep-Alive': 'timeout=%d, max=%d' % | |
22 (KEEP_ALIVE_TIMEOUT, | |
23 KEEP_ALIVE_MAX_REQUESTS), | |
24 'Connection': 'Keep-Alive'}) | |
25 if additional_headers: | |
26 headers.update(additional_headers) | |
27 if length is None: | |
28 length = len(msg) | |
29 headers['Content-Length'] = str(length) | |
30 | |
31 header_lines = ['HTTP/1.1 %d %s' % (code, | |
32 httplib.responses[code])] | |
33 header_lines.extend(['%s: %s' % (key, value) | |
34 for key, value in headers.items()]) | |
35 header_lines.extend(['', msg]) | |
36 content = '\r\n'.join(header_lines) | |
37 yield cosocket.until_sent(content) | |
38 | |
39 def until_http_file_sent(filename, block_size = BLOCK_SIZE): | |
40 ext = '.' + filename.split('.')[-1] | |
41 | |
42 if ext in mimetypes.types_map: | |
43 mimetype = mimetypes.types_map[ext] | |
44 else: | |
45 mimetype = 'text/plain' | |
46 | |
47 length = os.stat(filename).st_size | |
48 num_blocks = length / block_size | |
49 if length % block_size: | |
50 num_blocks += 1 | |
51 infile = open(filename, 'r') | |
52 | |
53 yield until_http_response_sent(mimetype = mimetype, | |
54 length = length) | |
55 | |
56 for i in range(num_blocks): | |
57 # TODO: This could be bad since we're reading the file | |
58 # synchronously. | |
59 block = infile.read(block_size) | |
60 yield cosocket.until_sent(block) | |
61 | |
62 class AsyncWebServer(object): | |
63 def __init__(self, addr, app): | |
64 self._num_connections = 0 | |
65 self._app = app | |
66 cosocket.AsyncChatCoroutine(self._server_coroutine(addr)) | |
67 | |
68 def _server_coroutine(self, bind_addr): | |
69 yield cosocket.until_listening(bind_addr) | |
70 while 1: | |
71 conn, addr = yield cosocket.until_connection_accepted() | |
72 cosocket.AsyncChatCoroutine(self._connection_coroutine(addr), | |
73 conn) | |
74 | |
75 def _connection_coroutine(self, addr): | |
76 self._num_connections += 1 | |
77 try: | |
78 if KEEP_ALIVE_ENABLED: | |
79 for i in range(KEEP_ALIVE_MAX_REQUESTS): | |
80 yield self._until_one_request_processed(addr) | |
81 else: | |
82 yield self._until_one_request_processed(addr) | |
83 finally: | |
84 logging.info('Closing connection to %s' % repr(addr)) | |
85 self._num_connections -= 1 | |
86 | |
87 def _until_one_request_processed(self, addr): | |
88 request = yield cosocket.until_received(terminator = '\r\n\r\n') | |
89 request = request.splitlines() | |
90 request_line = request[0] | |
91 logging.info("Request from %s: %s" % (addr, request_line)) | |
92 stringfile = cStringIO.StringIO('\n'.join(request[1:])) | |
93 headers = mimetools.Message(stringfile) | |
94 req_parts = request_line.split() | |
95 | |
96 yield self._app.until_request_processed(method = req_parts[0], | |
97 path = req_parts[1], | |
98 headers = headers, | |
99 addr = addr) |