Package web :: Package web :: Module httpserver
[hide private]
[frames] | no frames]

Source Code for Module web.web.httpserver

  1  __all__ = ["runsimple"] 
  2   
  3  import sys, os 
  4  from SimpleHTTPServer import SimpleHTTPRequestHandler 
  5   
  6  import webapi as web 
  7  import net 
  8  import utils 
  9   
10 -def runbasic(func, server_address=("0.0.0.0", 8080)):
11 """ 12 Runs a simple HTTP server hosting WSGI app `func`. The directory `static/` 13 is hosted statically. 14 15 Based on [WsgiServer][ws] from [Colin Stewart][cs]. 16 17 [ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html 18 [cs]: http://www.owlfish.com/ 19 """ 20 # Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/) 21 # Modified somewhat for simplicity 22 # Used under the modified BSD license: 23 # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 24 25 import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse 26 import socket, errno 27 import traceback 28 29 class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): 30 def run_wsgi_app(self): 31 protocol, host, path, parameters, query, fragment = \ 32 urlparse.urlparse('http://dummyhost%s' % self.path) 33 34 # we only use path, query 35 env = {'wsgi.version': (1, 0) 36 ,'wsgi.url_scheme': 'http' 37 ,'wsgi.input': self.rfile 38 ,'wsgi.errors': sys.stderr 39 ,'wsgi.multithread': 1 40 ,'wsgi.multiprocess': 0 41 ,'wsgi.run_once': 0 42 ,'REQUEST_METHOD': self.command 43 ,'REQUEST_URI': self.path 44 ,'PATH_INFO': path 45 ,'QUERY_STRING': query 46 ,'CONTENT_TYPE': self.headers.get('Content-Type', '') 47 ,'CONTENT_LENGTH': self.headers.get('Content-Length', '') 48 ,'REMOTE_ADDR': self.client_address[0] 49 ,'SERVER_NAME': self.server.server_address[0] 50 ,'SERVER_PORT': str(self.server.server_address[1]) 51 ,'SERVER_PROTOCOL': self.request_version 52 } 53 54 for http_header, http_value in self.headers.items(): 55 env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \ 56 http_value 57 58 # Setup the state 59 self.wsgi_sent_headers = 0 60 self.wsgi_headers = [] 61 62 try: 63 # We have there environment, now invoke the application 64 result = self.server.app(env, self.wsgi_start_response) 65 try: 66 try: 67 for data in result: 68 if data: 69 self.wsgi_write_data(data) 70 finally: 71 if hasattr(result, 'close'): 72 result.close() 73 except socket.error, socket_err: 74 # Catch common network errors and suppress them 75 if (socket_err.args[0] in \ 76 (errno.ECONNABORTED, errno.EPIPE)): 77 return 78 except socket.timeout, socket_timeout: 79 return 80 except: 81 print >> web.debug, traceback.format_exc(), 82 83 if (not self.wsgi_sent_headers): 84 # We must write out something! 85 self.wsgi_write_data(" ") 86 return
87 88 do_POST = run_wsgi_app 89 do_PUT = run_wsgi_app 90 do_DELETE = run_wsgi_app 91 92 def do_GET(self): 93 if self.path.startswith('/static/'): 94 SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) 95 else: 96 self.run_wsgi_app() 97 98 def wsgi_start_response(self, response_status, response_headers, 99 exc_info=None): 100 if (self.wsgi_sent_headers): 101 raise Exception \ 102 ("Headers already sent and start_response called again!") 103 # Should really take a copy to avoid changes in the application.... 104 self.wsgi_headers = (response_status, response_headers) 105 return self.wsgi_write_data 106 107 def wsgi_write_data(self, data): 108 if (not self.wsgi_sent_headers): 109 status, headers = self.wsgi_headers 110 # Need to send header prior to data 111 status_code = status[:status.find(' ')] 112 status_msg = status[status.find(' ') + 1:] 113 self.send_response(int(status_code), status_msg) 114 for header, value in headers: 115 self.send_header(header, value) 116 self.end_headers() 117 self.wsgi_sent_headers = 1 118 # Send the data 119 self.wfile.write(data) 120 121 class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): 122 def __init__(self, func, server_address): 123 BaseHTTPServer.HTTPServer.__init__(self, 124 server_address, 125 WSGIHandler) 126 self.app = func 127 self.serverShuttingDown = 0 128 129 print "http://%s:%d/" % server_address 130 WSGIServer(func, server_address).serve_forever() 131
132 -def runsimple(func, server_address=("0.0.0.0", 8080)):
133 """ 134 Runs [CherryPy][cp] WSGI server hosting WSGI app `func`. 135 The directory `static/` is hosted statically. 136 137 [cp]: http://www.cherrypy.org 138 """ 139 func = StaticMiddleware(func) 140 func = LogMiddleware(func) 141 142 server = WSGIServer(server_address, func) 143 144 print "http://%s:%d/" % server_address 145 try: 146 server.start() 147 except KeyboardInterrupt: 148 server.stop()
149
150 -def WSGIServer(server_address, wsgi_app):
151 """Creates CherryPy WSGI server listening at `server_address` to serve `wsgi_app`. 152 This function can be overwritten to customize the webserver or use a different webserver. 153 """ 154 from wsgiserver import CherryPyWSGIServer 155 return CherryPyWSGIServer(server_address, wsgi_app, server_name="localhost")
156
157 -class StaticApp(SimpleHTTPRequestHandler):
158 """WSGI application for serving static files."""
159 - def __init__(self, environ, start_response):
160 self.headers = [] 161 self.environ = environ 162 self.start_response = start_response
163
164 - def send_response(self, status, msg=""):
165 self.status = str(status) + " " + msg
166
167 - def send_header(self, name, value):
168 self.headers.append((name, value))
169
170 - def end_headers(self):
171 pass
172
173 - def log_message(*a): pass
174
175 - def __iter__(self):
176 environ = self.environ 177 178 self.path = environ.get('PATH_INFO', '') 179 self.client_address = environ.get('REMOTE_ADDR','-'), \ 180 environ.get('REMOTE_PORT','-') 181 self.command = environ.get('REQUEST_METHOD', '-') 182 183 from cStringIO import StringIO 184 self.wfile = StringIO() # for capturing error 185 186 try: 187 path = self.translate_path(self.path) 188 etag = '"%s"' % os.path.getmtime(path) 189 client_etag = environ.get('HTTP_IF_NONE_MATCH') 190 self.send_header('ETag', etag) 191 if etag == client_etag: 192 self.send_response(304, "Not Modified") 193 self.start_response(self.status, self.headers) 194 raise StopIteration 195 except OSError: 196 pass # Probably a 404 197 198 f = self.send_head() 199 self.start_response(self.status, self.headers) 200 201 if f: 202 block_size = 16 * 1024 203 while True: 204 buf = f.read(block_size) 205 if not buf: 206 break 207 yield buf 208 f.close() 209 else: 210 value = self.wfile.getvalue() 211 yield value
212
213 -class StaticMiddleware:
214 """WSGI middleware for serving static files."""
215 - def __init__(self, app, prefix='/static/'):
216 self.app = app 217 self.prefix = prefix
218
219 - def __call__(self, environ, start_response):
220 path = environ.get('PATH_INFO', '') 221 if path.startswith(self.prefix): 222 return StaticApp(environ, start_response) 223 else: 224 return self.app(environ, start_response)
225
226 -class LogMiddleware:
227 """WSGI middleware for logging the status."""
228 - def __init__(self, app):
229 self.app = app 230 self.format = '%s - - [%s] "%s %s %s" - %s' 231 232 from BaseHTTPServer import BaseHTTPRequestHandler 233 import StringIO 234 f = StringIO.StringIO() 235 236 class FakeSocket: 237 def makefile(self, *a): 238 return f
239 240 # take log_date_time_string method from BaseHTTPRequestHandler 241 self.log_date_time_string = BaseHTTPRequestHandler(FakeSocket(), None, None).log_date_time_string 242
243 - def __call__(self, environ, start_response):
244 def xstart_response(status, response_headers, *args): 245 out = start_response(status, response_headers, *args) 246 self.log(status, environ) 247 return out
248 249 return self.app(environ, xstart_response) 250
251 - def log(self, status, environ):
252 outfile = environ.get('wsgi.errors', web.debug) 253 req = environ.get('PATH_INFO', '_') 254 protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-') 255 method = environ.get('REQUEST_METHOD', '-') 256 host = "%s:%s" % (environ.get('REMOTE_ADDR','-'), 257 environ.get('REMOTE_PORT','-')) 258 259 time = self.log_date_time_string() 260 261 msg = self.format % (host, time, protocol, method, req, status) 262 print >> outfile, utils.safestr(msg)
263