1
2
3
4
5
6 from BaseHTTPServer import BaseHTTPRequestHandler
7 import asynchat
8 import asyncore
9 import socket
10 import cStringIO
11 import re
12 import sys
13 import uuid
14
15 import logging
16 log = logging.getLogger("tangled.server")
17
18 __version__ = "0.1.1.1"
19
21 """
22 The request object that gets passed to a handler in order to respond
23 to a request.
24
25 @ivar client_address: tuple of address, port
26 @ivar method: "GET", "PUT", etc.
27 @ivar path: The uri of the request, /foo/bar
28 @ivar data: The body of the request
29 @ivar groups: This contains the groups (if any) from the regex used when registering the request handler
30 """
31 - def __init__(self, client_address, method, path, headers, data, groups):
32 self.client_address = client_address
33 self.method = method
34 self.path = path
35 self.headers = headers
36 self.data = data
37 self.groups = groups
38
39
41 """
42 The response object returned by a request handler.
43 """
44 - def __init__(self, code=None, headers=None, data=None):
45 """
46 @param code: A numeric HTTP status
47 @param headers: A dictionary containing header/content pairs
48 @param data: A str (or equivalent) containing the data
49 """
50 self.code = code or 200
51 self.headers = headers or {}
52 self.data = data or ""
53
54
56 """
57 An asynchronous HTTP request handler inspired somewhat by the
58 http://code.activestate.com/recipes/440665-asynchronous-http-server/
59 recipe.
60 """
61
62 server_version = "Tangled/" + __version__
63 methods = ["HEAD", "GET", "POST", "PUT", "DELETE", "TRACE", "OPTIONS", "CONNECT", "PATCH"]
64
70
72 asynchat.async_chat.__init__(self, conn)
73 self.client_address = addr
74 self.connection = conn
75 self.server = server
76 self.urlhandlers = self.server.urlhandlers
77
78
79
80 self.set_terminator ('\r\n\r\n')
81 self.incoming = []
82 self.rfile = None
83 self.wfile = AsyncHTTPRequestHandler.Pusher(self)
84 self.found_terminator = self.handle_request_line
85 self.protocol_version = "HTTP/1.1"
86 self.code = None
87
89 self.incoming.append(data)
90
92
93 self.rfile = cStringIO.StringIO(''.join(self.incoming))
94 self.incoming = []
95 self.rfile.seek(0)
96
98 """Prepare for reading the request body"""
99 bytesremaining = int(self.headers.getheader('content-length'))
100
101 self.set_terminator(bytesremaining)
102 self.incoming = []
103
104 self.found_terminator = self.handle_request_data
105
108
117
119 """
120 Called with the request handler's response
121
122 @param response: The response to the request
123 @type response: L{Response}
124 """
125 self.send_response(response.code)
126 for k, v in response.headers.items():
127 self.send_header(k, v)
128 if not response.data:
129 if "Content-Length" not in response.headers:
130 self.send_header("Content-Length", "0")
131 self.end_headers()
132 else:
133 if isinstance(response.data, list):
134 boundary = str(uuid.uuid4())
135 self.send_header("Content-Type", "multipart/mixed; boundary=%s"%boundary)
136
137 multidata = []
138 for data in response.data:
139 multidata.append("\r\n--%s\r\n"%boundary)
140 multidata.append("Content-Type: text/plain\r\n\r\n")
141 multidata.append(data)
142 multidata.append("\r\n--%s--\r\n"%boundary)
143 multidata = "".join(multidata)
144 self.send_header("Content-Length", len(multidata))
145 self.end_headers()
146 self.push(multidata)
147 else:
148 if "Content-Length" not in response.headers:
149 self.send_header("Content-Length", "%d"%len(response.data))
150 self.end_headers()
151 self.push(response.data)
152
153 self.close_when_done()
154
156 """Dispatch the request to a handler"""
157 for r, cls in self.urlhandlers:
158 m = re.match(r, self.path)
159 if m is not None:
160 try:
161 h = cls(self.server.context)
162 handler = getattr(h, "do_" + self.command)
163 d = handler(Request(self.client_address,
164 self.command,
165 self.path,
166 self.headers,
167 self.rfile.read(),
168 m.groups()))
169 d.add_callback(self.finish_request)
170 except AttributeError:
171 raise
172
173 self.send_header("Allow", ", ".join([method for method in self.methods if hasattr(h, "do_" + method)]))
174 self.send_error(405)
175 self.end_headers()
176 self.close_when_done()
177 return
178
179 self.send_error(404)
180 self.end_headers()
181 self.close_when_done()
182
184 """Called when the http request line and headers have been received"""
185
186 self.create_rfile()
187 self.raw_requestline = self.rfile.readline()
188 self.parse_request()
189
190 if self.command in ["PUT", "POST"]:
191
192 self.prepare_request()
193 else:
194 self.handle_request()
195
197 log.info(format, *args)
198
200 self.send_response(response.code)
201 for k, v in response.headers.items():
202 self.send_header(k, v)
203 self.end_headers()
204 if self.data:
205 self.push(data)
206 self.close_when_done()
207
209 self.send_error(error)
210 self.close_when_done()
211
212
214 """
215 Cobbled together from various sources, most of them state that they
216 copied from the Medusa http server..
217 """
218 - def __init__(self, address, context, urlhandlers):
219 """
220 @param address: Tuple of address, port
221 @param context: Something that gets passed to the handler's constructor for each request
222 @param urlhandlers: list of (regex, handler) tuples.
223
224 A handler needs to have a constructor that accepts the context object,
225 and a do_* method for each HTTP verb it wants to handle.
226 """
227 self.context = context
228 self.urlhandlers = [(re.compile(r), h) for r, h in urlhandlers]
229 self.address = address
230 asyncore.dispatcher.__init__(self)
231 self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
232 self.set_reuse_addr()
233 self.bind(self.address)
234 self.listen(5)
235
237 try:
238 conn, addr = self.accept()
239 except socket.error:
240 log.exception('server accept() threw an exception')
241 return
242 except TypeError:
243 log.exception('server accept() threw EWOULDBLOCK')
244 return
245
246
247 AsyncHTTPRequestHandler(conn, addr, self)
248
249 if __name__=="__main__":
250 import core
261 server = AsyncHTTPServer(("localhost", 8080), None, [("/multipart", MultiPartHandler), ("/normal", NormalHandler)])
262 asyncore.loop()
263