1
# -*- coding: utf-8 -*-
6
There are many ways to serve a WSGI application. While you're developing
7
it you usually don't want a full blown webserver like Apache but a simple
8
standalone one. From Python 2.5 onwards there is the `wsgiref`_ server in
9
the standard library. If you're using older versions of Python you can
10
download the package from the cheeseshop.
12
However there are some caveats. Sourcecode won't reload itself when
13
changed and each time you kill the server using ``^C`` you get an
14
`KeyboardInterrupt` error. While the latter is easy to solve the first
15
one can be a pain in the ass in some situations.
17
Because of that Werkzeug ships a small wrapper over `wsgiref` that spawns
18
the WSGI application in a subprocess and automatically reloads the
19
application if a module was changed.
21
The easiest way is creating a small ``start-myproject.py`` that runs the
25
# -*- coding: utf-8 -*-
26
from myproject import make_app
27
from werkzeug import run_simple
30
run_simple('localhost', 8080, app, use_reloader=True)
32
You can also pass it a `extra_files` keyword argument with a list of
33
additional files (like configuration files) you want to observe.
35
For bigger applications you should consider using `werkzeug.script`
36
instead of a simple start file.
38
.. _wsgiref: http://cheeseshop.python.org/pypi/wsgiref
41
:copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details.
42
:license: BSD, see LICENSE for more details.
50
from urllib import unquote
51
from urlparse import urlparse
52
from itertools import chain
53
from SocketServer import ThreadingMixIn, ForkingMixIn
54
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
56
from werkzeug import __version__ as version
57
from werkzeug._internal import _log
58
from werkzeug.utils import responder
59
from werkzeug.exceptions import InternalServerError
62
class BaseRequestHandler(BaseHTTPRequestHandler, object):
63
server_version = 'Werkzeug/' + version
65
def make_environ(self):
66
path_info, query = urlparse(self.path)[2::2]
68
'wsgi.version': (1, 0),
69
'wsgi.url_scheme': 'http',
70
'wsgi.input': self.rfile,
71
'wsgi.errors': sys.stderr,
72
'wsgi.multithread': self.server.multithread,
73
'wsgi.multiprocess': self.server.multiprocess,
74
'wsgi.run_once': False,
75
'REQUEST_METHOD': self.command,
77
'PATH_INFO': unquote(path_info),
78
'QUERY_STRING': query,
79
'CONTENT_TYPE': self.headers.get('Content-Type', ''),
80
'CONTENT_LENGTH': self.headers.get('Content-Length', ''),
81
'REMOTE_ADDR': self.client_address[0],
82
'REMOTE_PORT': self.client_address[1],
83
'SERVER_NAME': self.server.server_address[0],
84
'SERVER_PORT': str(self.server.server_address[1]),
85
'SERVER_PROTOCOL': self.request_version
88
for key, value in self.headers.items():
89
key = 'HTTP_' + key.upper().replace('-', '_')
90
if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
97
environ = self.make_environ()
102
assert headers_set, 'write() before start_response'
104
status, response_headers = headers_sent[:] = headers_set
105
code, msg = status.split(None, 1)
106
self.send_response(int(code), msg)
108
for key, value in response_headers:
109
self.send_header(key, value)
112
if 'content-length' not in header_keys:
113
self.close_connection = True
114
self.send_header('Connection', 'close')
115
if 'server' not in header_keys:
116
self.send_header('Server', self.version_string())
117
if 'date' not in header_keys:
118
self.send_header('Date', self.date_time_string())
121
assert type(data) is str, 'applications must write bytes'
122
self.wfile.write(data)
125
def start_response(status, response_headers, exc_info=None):
129
raise exc_info[0], exc_info[1], exc_info[2]
133
raise AssertionError('Headers already set')
134
headers_set[:] = [status, response_headers]
138
application_iter = app(environ, start_response)
140
for data in application_iter:
142
# make sure the headers are sent
146
if hasattr(application_iter, 'close'):
147
application_iter.close()
148
application_iter = None
152
except (socket.error, socket.timeout), e:
153
self.connection_dropped(e, environ)
155
if self.server.passthrough_errors:
157
from werkzeug.debug.tbtools import get_current_traceback
158
traceback = get_current_traceback(ignore_system_exceptions=True)
160
# if we haven't yet sent the headers but they are set
161
# we roll back to be able to set them again.
164
execute(InternalServerError())
167
self.server.log('error', 'Error on request:\n%s',
170
def connection_dropped(self, error, environ):
171
"""Called if the connection was closed by the client. By default
175
def handle_one_request(self):
176
"""Handle a single HTTP request."""
177
self.raw_requestline = self.rfile.readline()
178
if not self.raw_requestline:
179
self.close_connection = 1
180
elif self.parse_request():
181
return self.run_wsgi()
183
def send_response(self, code, message=None):
184
"""Send the response header and log the response code."""
185
self.log_request(code)
187
message = code in self.responses and self.responses[code][0] or ''
188
if self.request_version != 'HTTP/0.9':
189
self.wfile.write("%s %d %s\r\n" %
190
(self.protocol_version, code, message))
192
def version_string(self):
193
return BaseHTTPRequestHandler.version_string(self).strip()
195
def address_string(self):
196
return self.client_address[0]
199
class BaseWSGIServer(HTTPServer):
203
def __init__(self, host, port, app, handler=None,
204
passthrough_errors=False):
206
handler = BaseRequestHandler
207
HTTPServer.__init__(self, (host, int(port)), handler)
209
self.passthrough_errors = passthrough_errors
211
def log(self, type, message, *args):
212
_log(type, message, *args)
214
def serve_forever(self):
216
HTTPServer.serve_forever(self)
217
except KeyboardInterrupt:
220
def handle_error(self, request, client_address):
221
if self.passthrough_errors:
224
return HTTPServer.handle_error(self, request, client_address)
227
class ThreadedWSGIServer(ThreadingMixIn, BaseWSGIServer):
231
class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer):
234
def __init__(self, host, port, app, processes=40, handler=None,
235
passthrough_errors=False):
236
BaseWSGIServer.__init__(self, host, port, app, handler,
238
self.max_children = processes
241
def make_server(host, port, app=None, threaded=False, processes=1,
242
request_handler=None, passthrough_errors=False):
243
"""Create a new server instance that is either threaded, or forks
244
or just processes one request after another.
246
if threaded and processes > 1:
247
raise ValueError("cannot have a multithreaded and "
248
"multi process server.")
250
return ThreadedWSGIServer(host, port, app, request_handler,
253
return ForkingWSGIServer(host, port, app, processes, request_handler,
256
return BaseWSGIServer(host, port, app, request_handler,
260
def reloader_loop(extra_files=None, interval=1):
261
"""When this function is run from the main thread, it will force other
262
threads to exit when any modules currently loaded change.
264
Copyright notice. This function is based on the autoreload.py from
265
the CherryPy trac which originated from WSGIKit which is now dead.
267
:param extra_files: a list of additional files it should watch.
269
def iter_module_files():
270
for module in sys.modules.values():
271
filename = getattr(module, '__file__', None)
273
while not os.path.isfile(filename):
274
filename = os.path.dirname(filename)
278
if filename[-4:] in ('.pyc', '.pyo'):
279
filename = filename[:-1]
284
for filename in chain(iter_module_files(), extra_files or ()):
286
mtime = os.stat(filename).st_mtime
290
old_time = mtimes.get(filename)
292
mtimes[filename] = mtime
294
elif mtime > old_time:
295
_log('info', ' * Detected change in %r, reloading' % filename)
300
def restart_with_reloader():
301
"""Spawn a new Python interpreter with the same arguments as this one,
302
but running the reloader thread.
305
_log('info', ' * Restarting with reloader...')
306
args = [sys.executable] + sys.argv
307
new_environ = os.environ.copy()
308
new_environ['WERKZEUG_RUN_MAIN'] = 'true'
309
exit_code = subprocess.call(args, env=new_environ)
314
def run_with_reloader(main_func, extra_files=None, interval=1):
315
"""Run the given function in an independent python interpreter."""
316
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
317
thread.start_new_thread(main_func, ())
319
reloader_loop(extra_files, interval)
320
except KeyboardInterrupt:
323
sys.exit(restart_with_reloader())
324
except KeyboardInterrupt:
328
def run_simple(hostname, port, application, use_reloader=False,
329
use_debugger=False, use_evalex=True,
330
extra_files=None, reloader_interval=1, threaded=False,
331
processes=1, request_handler=None, static_files=None,
332
passthrough_errors=False):
333
"""Start an application using wsgiref and with an optional reloader. This
334
wraps `wsgiref` to fix the wrong default reporting of the multithreaded
335
WSGI variable and adds optional multithreading and fork support.
337
.. versionadded:: 0.5
338
`static_files` was added to simplify serving of static files as well
339
as `passthrough_errors`.
341
:param hostname: The host for the application. eg: ``'localhost'``
342
:param port: The port for the server. eg: ``8080``
343
:param application: the WSGI application to execute
344
:param use_reloader: should the server automatically restart the python
345
process if modules were changed?
346
:param use_debugger: should the werkzeug debugging system be used?
347
:param use_evalex: should the exception evaluation feature be enabled?
348
:param extra_files: a list of files the reloader should watch
349
additionally to the modules. For example configuration
351
:param reloader_interval: the interval for the reloader in seconds.
352
:param threaded: should the process handle each request in a separate
354
:param processes: number of processes to spawn.
355
:param request_handler: optional parameter that can be used to replace
356
the default one. You can use this to replace it
358
:class:`~BaseHTTPServer.BaseHTTPRequestHandler`
360
:param static_files: a dict of paths for static files. This works exactly
361
like :class:`SharedDataMiddleware`, it's actually
362
just wrapping the application in that middleware before
364
:param passthrough_errors: set this to `True` to disable the error catching.
365
This means that the server will die on errors but
366
it can be useful to hook debuggers in (pdb etc.)
369
from werkzeug.debug import DebuggedApplication
370
application = DebuggedApplication(application, use_evalex)
372
from werkzeug.utils import SharedDataMiddleware
373
application = SharedDataMiddleware(application, static_files)
376
make_server(hostname, port, application, threaded,
377
processes, request_handler,
378
passthrough_errors).serve_forever()
380
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
381
display_hostname = hostname or '127.0.0.1'
382
_log('info', ' * Running on http://%s:%d/', display_hostname, port)
384
# Create and destroy a socket so that any exceptions are raised before
385
# we spawn a separate Python interpreter and lose this ability.
386
test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
387
test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
388
test_socket.bind((hostname, port))
390
run_with_reloader(inner, extra_files, reloader_interval)