36 |
kaklik |
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()
|