1 __all__ = ["runsimple"]
4 from SimpleHTTPServer import SimpleHTTPRequestHandler
12 def runbasic(func, server_address=("0.0.0.0", 8080)):
14 Runs a simple HTTP server hosting WSGI app `func`. The directory `static/`
17 Based on [WsgiServer][ws] from [Colin Stewart][cs].
19 [ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html
20 [cs]: http://www.owlfish.com/
22 # Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/)
23 # Modified somewhat for simplicity
24 # Used under the modified BSD license:
25 # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
27 import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse
31 class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
32 def run_wsgi_app(self):
33 protocol, host, path, parameters, query, fragment = \
34 urlparse.urlparse('http://dummyhost%s' % self.path)
36 # we only use path, query
37 env = {'wsgi.version': (1, 0)
38 ,'wsgi.url_scheme': 'http'
39 ,'wsgi.input': self.rfile
40 ,'wsgi.errors': sys.stderr
41 ,'wsgi.multithread': 1
42 ,'wsgi.multiprocess': 0
44 ,'REQUEST_METHOD': self.command
45 ,'REQUEST_URI': self.path
47 ,'QUERY_STRING': query
48 ,'CONTENT_TYPE': self.headers.get('Content-Type', '')
49 ,'CONTENT_LENGTH': self.headers.get('Content-Length', '')
50 ,'REMOTE_ADDR': self.client_address[0]
51 ,'SERVER_NAME': self.server.server_address[0]
52 ,'SERVER_PORT': str(self.server.server_address[1])
53 ,'SERVER_PROTOCOL': self.request_version
56 for http_header, http_value in self.headers.items():
57 env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \
61 self.wsgi_sent_headers = 0
62 self.wsgi_headers = []
65 # We have there environment, now invoke the application
66 result = self.server.app(env, self.wsgi_start_response)
71 self.wsgi_write_data(data)
73 if hasattr(result, 'close'):
75 except socket.error, socket_err:
76 # Catch common network errors and suppress them
77 if (socket_err.args[0] in \
78 (errno.ECONNABORTED, errno.EPIPE)):
80 except socket.timeout, socket_timeout:
83 print >> web.debug, traceback.format_exc(),
85 if (not self.wsgi_sent_headers):
86 # We must write out something!
87 self.wsgi_write_data(" ")
90 do_POST = run_wsgi_app
92 do_DELETE = run_wsgi_app
95 if self.path.startswith('/static/'):
96 SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
100 def wsgi_start_response(self, response_status, response_headers,
102 if (self.wsgi_sent_headers):
104 ("Headers already sent and start_response called again!")
105 # Should really take a copy to avoid changes in the application....
106 self.wsgi_headers = (response_status, response_headers)
107 return self.wsgi_write_data
109 def wsgi_write_data(self, data):
110 if (not self.wsgi_sent_headers):
111 status, headers = self.wsgi_headers
112 # Need to send header prior to data
113 status_code = status[:status.find(' ')]
114 status_msg = status[status.find(' ') + 1:]
115 self.send_response(int(status_code), status_msg)
116 for header, value in headers:
117 self.send_header(header, value)
119 self.wsgi_sent_headers = 1
121 self.wfile.write(data)
123 class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
124 def __init__(self, func, server_address):
125 BaseHTTPServer.HTTPServer.__init__(self,
129 self.serverShuttingDown = 0
131 print "http://%s:%d/" % server_address
132 WSGIServer(func, server_address).serve_forever()
134 # The WSGIServer instance.
135 # Made global so that it can be stopped in embedded mode.
138 def runsimple(func, server_address=("0.0.0.0", 8080)):
140 Runs [CherryPy][cp] WSGI server hosting WSGI app `func`.
141 The directory `static/` is hosted statically.
143 [cp]: http://www.cherrypy.org
146 func = StaticMiddleware(func)
147 func = LogMiddleware(func)
149 server = WSGIServer(server_address, func)
151 if server.ssl_adapter:
152 print "https://%s:%d/" % server_address
154 print "http://%s:%d/" % server_address
158 except (KeyboardInterrupt, SystemExit):
162 def WSGIServer(server_address, wsgi_app):
163 """Creates CherryPy WSGI server listening at `server_address` to serve `wsgi_app`.
164 This function can be overwritten to customize the webserver or use a different webserver.
168 # Default values of wsgiserver.ssl_adapters uses cherrypy.wsgiserver
169 # prefix. Overwriting it make it work with web.wsgiserver.
170 wsgiserver.ssl_adapters = {
171 'builtin': 'web.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
172 'pyopenssl': 'web.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
175 server = wsgiserver.CherryPyWSGIServer(server_address, wsgi_app, server_name="localhost")
177 def create_ssl_adapter(cert, key):
178 # wsgiserver tries to import submodules as cherrypy.wsgiserver.foo.
179 # That doesn't work as not it is web.wsgiserver.
180 # Patching sys.modules temporarily to make it work.
182 cherrypy = types.ModuleType('cherrypy')
183 cherrypy.wsgiserver = wsgiserver
184 sys.modules['cherrypy'] = cherrypy
185 sys.modules['cherrypy.wsgiserver'] = wsgiserver
187 from wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
188 adapter = pyOpenSSLAdapter(cert, key)
190 # We are done with our work. Cleanup the patches.
191 del sys.modules['cherrypy']
192 del sys.modules['cherrypy.wsgiserver']
196 # SSL backward compatibility
197 if (server.ssl_adapter is None and
198 getattr(server, 'ssl_certificate', None) and
199 getattr(server, 'ssl_private_key', None)):
200 server.ssl_adapter = create_ssl_adapter(server.ssl_certificate, server.ssl_private_key)
202 server.nodelay = not sys.platform.startswith('java') # TCP_NODELAY isn't supported on the JVM
205 class StaticApp(SimpleHTTPRequestHandler):
206 """WSGI application for serving static files."""
207 def __init__(self, environ, start_response):
209 self.environ = environ
210 self.start_response = start_response
212 def send_response(self, status, msg=""):
213 self.status = str(status) + " " + msg
215 def send_header(self, name, value):
216 self.headers.append((name, value))
218 def end_headers(self):
221 def log_message(*a): pass
224 environ = self.environ
226 self.path = environ.get('PATH_INFO', '')
227 self.client_address = environ.get('REMOTE_ADDR','-'), \
228 environ.get('REMOTE_PORT','-')
229 self.command = environ.get('REQUEST_METHOD', '-')
231 from cStringIO import StringIO
232 self.wfile = StringIO() # for capturing error
235 path = self.translate_path(self.path)
236 etag = '"%s"' % os.path.getmtime(path)
237 client_etag = environ.get('HTTP_IF_NONE_MATCH')
238 self.send_header('ETag', etag)
239 if etag == client_etag:
240 self.send_response(304, "Not Modified")
241 self.start_response(self.status, self.headers)
244 pass # Probably a 404
247 self.start_response(self.status, self.headers)
250 block_size = 16 * 1024
252 buf = f.read(block_size)
258 value = self.wfile.getvalue()
261 class StaticMiddleware:
262 """WSGI middleware for serving static files."""
263 def __init__(self, app, prefix='/static/'):
267 def __call__(self, environ, start_response):
268 path = environ.get('PATH_INFO', '')
269 path = self.normpath(path)
271 if path.startswith(self.prefix):
272 return StaticApp(environ, start_response)
274 return self.app(environ, start_response)
276 def normpath(self, path):
277 path2 = posixpath.normpath(urllib.unquote(path))
278 if path.endswith("/"):
284 """WSGI middleware for logging the status."""
285 def __init__(self, app):
287 self.format = '%s - - [%s] "%s %s %s" - %s'
289 from BaseHTTPServer import BaseHTTPRequestHandler
291 f = StringIO.StringIO()
294 def makefile(self, *a):
297 # take log_date_time_string method from BaseHTTPRequestHandler
298 self.log_date_time_string = BaseHTTPRequestHandler(FakeSocket(), None, None).log_date_time_string
300 def __call__(self, environ, start_response):
301 def xstart_response(status, response_headers, *args):
302 out = start_response(status, response_headers, *args)
303 self.log(status, environ)
306 return self.app(environ, xstart_response)
308 def log(self, status, environ):
309 outfile = environ.get('wsgi.errors', web.debug)
310 req = environ.get('PATH_INFO', '_')
311 protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-')
312 method = environ.get('REQUEST_METHOD', '-')
313 host = "%s:%s" % (environ.get('REMOTE_ADDR','-'),
314 environ.get('REMOTE_PORT','-'))
316 time = self.log_date_time_string()
318 msg = self.format % (host, time, protocol, method, req, status)
319 print >> outfile, utils.safestr(msg)