Package tangled :: Module server
[hide private]
[frames] | no frames]

Source Code for Module tangled.server

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (c) 2001-2010 Pär Bohrarper. 
  4  # See LICENSE for details. 
  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   
20 -class Request(object):
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
40 -class Response(object):
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
55 -class AsyncHTTPRequestHandler(asynchat.async_chat, BaseHTTPRequestHandler):
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
65 - class Pusher(object):
66 - def __init__(self, obj):
67 self.obj = obj
68 - def write(self, data):
69 self.obj.push(data)
70
71 - def __init__(self, conn, addr, server):
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 # set the terminator : when it is received, this means that the 78 # http request is complete ; control will be passed to 79 # self.found_terminator 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
88 - def collect_incoming_data(self,data):
89 self.incoming.append(data)
90
91 - def create_rfile(self):
92 # BaseHTTPRequestHandler expects a file like object 93 self.rfile = cStringIO.StringIO(''.join(self.incoming)) 94 self.incoming = [] 95 self.rfile.seek(0)
96
97 - def prepare_request(self):
98 """Prepare for reading the request body""" 99 bytesremaining = int(self.headers.getheader('content-length')) 100 # set terminator to length (will read bytesremaining bytes) 101 self.set_terminator(bytesremaining) 102 self.incoming = [] 103 # control will be passed to a new found_terminator 104 self.found_terminator = self.handle_request_data
105
106 - def handle_junk(self):
107 pass
108
109 - def handle_request_data(self):
110 """Called when a request body has been read""" 111 self.create_rfile() 112 # set up so extra data is thrown away 113 self.set_terminator(None) 114 self.found_terminator = self.handle_junk 115 # Actually handle the request 116 self.handle_request()
117
118 - def finish_request(self, response):
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 # TODO: might be a good idea to use the 'email' module to create the message.. 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
155 - def handle_request(self):
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 # Method not supported 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 # No match found, send 404 179 self.send_error(404) 180 self.end_headers() 181 self.close_when_done()
182
183 - def handle_request_line(self):
184 """Called when the http request line and headers have been received""" 185 # prepare attributes needed in parse_request() 186 self.create_rfile() 187 self.raw_requestline = self.rfile.readline() 188 self.parse_request() 189 190 if self.command in ["PUT", "POST"]: 191 # Wait for the data to come in before processing the request 192 self.prepare_request() 193 else: 194 self.handle_request()
195
196 - def log_message(self, format, *args):
197 log.info(format, *args)
198
199 - def request_handled(self, response):
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
208 - def request_error(self, error):
209 self.send_error(error) 210 self.close_when_done()
211 212
213 -class AsyncHTTPServer(asyncore.dispatcher):
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
236 - def handle_accept(self):
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 # creates an instance of the handler class to handle the request/response 246 # on the incoming connection 247 AsyncHTTPRequestHandler(conn, addr, self)
248 249 if __name__=="__main__": 250 import core
251 - class MultiPartHandler(object):
252 - def __init__(self, context):
253 pass
254 - def do_GET(self, request):
255 return core.succeed(Response(300, None, ["elephant", "giraffe", "lion"]))
256 - class NormalHandler(object):
257 - def __init__(self, context):
258 pass
259 - def do_GET(self, request):
260 return core.succeed(Response(200, None, "elephant\ngiraffe\nlion"))
261 server = AsyncHTTPServer(("localhost", 8080), None, [("/multipart", MultiPartHandler), ("/normal", NormalHandler)]) 262 asyncore.loop() 263