]>
Commit | Line | Data |
---|---|---|
879aa787 MT |
1 | # Written by Bram Cohen |
2 | # see LICENSE.txt for license information | |
3 | ||
4 | from cStringIO import StringIO | |
5 | from sys import stdout | |
6 | import time | |
7 | from clock import clock | |
8 | from gzip import GzipFile | |
9 | try: | |
10 | True | |
11 | except: | |
12 | True = 1 | |
13 | False = 0 | |
14 | ||
15 | DEBUG = False | |
16 | ||
17 | weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] | |
18 | ||
19 | months = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', | |
20 | 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] | |
21 | ||
22 | class HTTPConnection: | |
23 | def __init__(self, handler, connection): | |
24 | self.handler = handler | |
25 | self.connection = connection | |
26 | self.buf = '' | |
27 | self.closed = False | |
28 | self.done = False | |
29 | self.donereading = False | |
30 | self.next_func = self.read_type | |
31 | ||
32 | def get_ip(self): | |
33 | return self.connection.get_ip() | |
34 | ||
35 | def data_came_in(self, data): | |
36 | if self.donereading or self.next_func is None: | |
37 | return True | |
38 | self.buf += data | |
39 | while True: | |
40 | try: | |
41 | i = self.buf.index('\n') | |
42 | except ValueError: | |
43 | return True | |
44 | val = self.buf[:i] | |
45 | self.buf = self.buf[i+1:] | |
46 | self.next_func = self.next_func(val) | |
47 | if self.donereading: | |
48 | return True | |
49 | if self.next_func is None or self.closed: | |
50 | return False | |
51 | ||
52 | def read_type(self, data): | |
53 | self.header = data.strip() | |
54 | words = data.split() | |
55 | if len(words) == 3: | |
56 | self.command, self.path, garbage = words | |
57 | self.pre1 = False | |
58 | elif len(words) == 2: | |
59 | self.command, self.path = words | |
60 | self.pre1 = True | |
61 | if self.command != 'GET': | |
62 | return None | |
63 | else: | |
64 | return None | |
65 | if self.command not in ('HEAD', 'GET'): | |
66 | return None | |
67 | self.headers = {} | |
68 | return self.read_header | |
69 | ||
70 | def read_header(self, data): | |
71 | data = data.strip() | |
72 | if data == '': | |
73 | self.donereading = True | |
74 | if self.headers.get('accept-encoding','').find('gzip') > -1: | |
75 | self.encoding = 'gzip' | |
76 | else: | |
77 | self.encoding = 'identity' | |
78 | r = self.handler.getfunc(self, self.path, self.headers) | |
79 | if r is not None: | |
80 | self.answer(r) | |
81 | return None | |
82 | try: | |
83 | i = data.index(':') | |
84 | except ValueError: | |
85 | return None | |
86 | self.headers[data[:i].strip().lower()] = data[i+1:].strip() | |
87 | if DEBUG: | |
88 | print data[:i].strip() + ": " + data[i+1:].strip() | |
89 | return self.read_header | |
90 | ||
91 | def answer(self, (responsecode, responsestring, headers, data)): | |
92 | if self.closed: | |
93 | return | |
94 | if self.encoding == 'gzip': | |
95 | compressed = StringIO() | |
96 | gz = GzipFile(fileobj = compressed, mode = 'wb', compresslevel = 9) | |
97 | gz.write(data) | |
98 | gz.close() | |
99 | cdata = compressed.getvalue() | |
100 | if len(cdata) >= len(data): | |
101 | self.encoding = 'identity' | |
102 | else: | |
103 | if DEBUG: | |
104 | print "Compressed: %i Uncompressed: %i\n" % (len(cdata),len(data)) | |
105 | data = cdata | |
106 | headers['Content-Encoding'] = 'gzip' | |
107 | ||
108 | # i'm abusing the identd field here, but this should be ok | |
109 | if self.encoding == 'identity': | |
110 | ident = '-' | |
111 | else: | |
112 | ident = self.encoding | |
113 | self.handler.log( self.connection.get_ip(), ident, '-', | |
114 | self.header, responsecode, len(data), | |
115 | self.headers.get('referer','-'), | |
116 | self.headers.get('user-agent','-') ) | |
117 | self.done = True | |
118 | r = StringIO() | |
119 | r.write('HTTP/1.0 ' + str(responsecode) + ' ' + | |
120 | responsestring + '\r\n') | |
121 | if not self.pre1: | |
122 | headers['Content-Length'] = len(data) | |
123 | for key, value in headers.items(): | |
124 | r.write(key + ': ' + str(value) + '\r\n') | |
125 | r.write('\r\n') | |
126 | if self.command != 'HEAD': | |
127 | r.write(data) | |
128 | self.connection.write(r.getvalue()) | |
129 | if self.connection.is_flushed(): | |
130 | self.connection.shutdown(1) | |
131 | ||
132 | class HTTPHandler: | |
133 | def __init__(self, getfunc, minflush): | |
134 | self.connections = {} | |
135 | self.getfunc = getfunc | |
136 | self.minflush = minflush | |
137 | self.lastflush = clock() | |
138 | ||
139 | def external_connection_made(self, connection): | |
140 | self.connections[connection] = HTTPConnection(self, connection) | |
141 | ||
142 | def connection_flushed(self, connection): | |
143 | if self.connections[connection].done: | |
144 | connection.shutdown(1) | |
145 | ||
146 | def connection_lost(self, connection): | |
147 | ec = self.connections[connection] | |
148 | ec.closed = True | |
149 | del ec.connection | |
150 | del ec.next_func | |
151 | del self.connections[connection] | |
152 | ||
153 | def data_came_in(self, connection, data): | |
154 | c = self.connections[connection] | |
155 | if not c.data_came_in(data) and not c.closed: | |
156 | c.connection.shutdown(1) | |
157 | ||
158 | def log(self, ip, ident, username, header, | |
159 | responsecode, length, referrer, useragent): | |
160 | year, month, day, hour, minute, second, a, b, c = time.localtime(time.time()) | |
161 | print '%s %s %s [%02d/%3s/%04d:%02d:%02d:%02d] "%s" %i %i "%s" "%s"' % ( | |
162 | ip, ident, username, day, months[month], year, hour, | |
163 | minute, second, header, responsecode, length, referrer, useragent) | |
164 | t = clock() | |
165 | if t - self.lastflush > self.minflush: | |
166 | self.lastflush = t | |
167 | stdout.flush() |