~ubuntu-branches/ubuntu/oneiric/moin/oneiric-security

« back to all changes in this revision

Viewing changes to MoinMoin/support/werkzeug/serving.py

  • Committer: Bazaar Package Importer
  • Author(s): Jamie Strandboge
  • Date: 2010-03-30 12:55:34 UTC
  • mfrom: (0.1.17 sid)
  • Revision ID: james.westby@ubuntu.com-20100330125534-4c2ufc1rok24447l
Tags: 1.9.2-2ubuntu1
* Merge from Debian testing (LP: #521834). Based on work by Stefan Ebner.
  Remaining changes:
 - Remove python-xml from Suggests field, the package isn't anymore in
   sys.path.
 - Demote fckeditor from Recommends to Suggests; the code was previously
   embedded in moin, but it was also disabled, so there's no reason for us
   to pull this in by default currently. Note: This isn't necessary anymore
   but needs a MIR for fckeditor, so postpone dropping this change until
   lucid+1
* debian/rules:
  - Replace hardcoded python2.5 with python* and hardcore python2.6 for ln
* debian/control.in: drop versioned depends on cdbs

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
"""
 
3
    werkzeug.serving
 
4
    ~~~~~~~~~~~~~~~~
 
5
 
 
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.
 
11
 
 
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.
 
16
 
 
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.
 
20
 
 
21
    The easiest way is creating a small ``start-myproject.py`` that runs the
 
22
    application::
 
23
 
 
24
        #!/usr/bin/env python
 
25
        # -*- coding: utf-8 -*-
 
26
        from myproject import make_app
 
27
        from werkzeug import run_simple
 
28
 
 
29
        app = make_app(...)
 
30
        run_simple('localhost', 8080, app, use_reloader=True)
 
31
 
 
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.
 
34
 
 
35
    For bigger applications you should consider using `werkzeug.script`
 
36
    instead of a simple start file.
 
37
 
 
38
    .. _wsgiref: http://cheeseshop.python.org/pypi/wsgiref
 
39
 
 
40
 
 
41
    :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details.
 
42
    :license: BSD, see LICENSE for more details.
 
43
"""
 
44
import os
 
45
import socket
 
46
import sys
 
47
import time
 
48
import thread
 
49
import subprocess
 
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
 
55
 
 
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
 
60
 
 
61
 
 
62
class BaseRequestHandler(BaseHTTPRequestHandler, object):
 
63
    server_version = 'Werkzeug/' + version
 
64
 
 
65
    def make_environ(self):
 
66
        path_info, query = urlparse(self.path)[2::2]
 
67
        environ = {
 
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,
 
76
            'SCRIPT_NAME':          '',
 
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
 
86
        }
 
87
 
 
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'):
 
91
                environ[key] = value
 
92
 
 
93
        return environ
 
94
 
 
95
    def run_wsgi(self):
 
96
        app = self.server.app
 
97
        environ = self.make_environ()
 
98
        headers_set = []
 
99
        headers_sent = []
 
100
 
 
101
        def write(data):
 
102
            assert headers_set, 'write() before start_response'
 
103
            if not headers_sent:
 
104
                status, response_headers = headers_sent[:] = headers_set
 
105
                code, msg = status.split(None, 1)
 
106
                self.send_response(int(code), msg)
 
107
                header_keys = set()
 
108
                for key, value in response_headers:
 
109
                    self.send_header(key, value)
 
110
                    key = key.lower()
 
111
                    header_keys.add(key)
 
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())
 
119
                self.end_headers()
 
120
 
 
121
            assert type(data) is str, 'applications must write bytes'
 
122
            self.wfile.write(data)
 
123
            self.wfile.flush()
 
124
 
 
125
        def start_response(status, response_headers, exc_info=None):
 
126
            if exc_info:
 
127
                try:
 
128
                    if headers_sent:
 
129
                        raise exc_info[0], exc_info[1], exc_info[2]
 
130
                finally:
 
131
                    exc_info = None
 
132
            elif headers_set:
 
133
                raise AssertionError('Headers already set')
 
134
            headers_set[:] = [status, response_headers]
 
135
            return write
 
136
 
 
137
        def execute(app):
 
138
            application_iter = app(environ, start_response)
 
139
            try:
 
140
                for data in application_iter:
 
141
                    write(data)
 
142
                # make sure the headers are sent
 
143
                if not headers_sent:
 
144
                    write('')
 
145
            finally:
 
146
                if hasattr(application_iter, 'close'):
 
147
                    application_iter.close()
 
148
                application_iter = None
 
149
 
 
150
        try:
 
151
            execute(app)
 
152
        except (socket.error, socket.timeout), e:
 
153
            self.connection_dropped(e, environ)
 
154
        except:
 
155
            if self.server.passthrough_errors:
 
156
                raise
 
157
            from werkzeug.debug.tbtools import get_current_traceback
 
158
            traceback = get_current_traceback(ignore_system_exceptions=True)
 
159
            try:
 
160
                # if we haven't yet sent the headers but they are set
 
161
                # we roll back to be able to set them again.
 
162
                if not headers_sent:
 
163
                    del headers_set[:]
 
164
                execute(InternalServerError())
 
165
            except:
 
166
                pass
 
167
            self.server.log('error', 'Error on request:\n%s',
 
168
                            traceback.plaintext)
 
169
 
 
170
    def connection_dropped(self, error, environ):
 
171
        """Called if the connection was closed by the client.  By default
 
172
        nothing happens.
 
173
        """
 
174
 
 
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()
 
182
 
 
183
    def send_response(self, code, message=None):
 
184
        """Send the response header and log the response code."""
 
185
        self.log_request(code)
 
186
        if message is None:
 
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))
 
191
 
 
192
    def version_string(self):
 
193
        return BaseHTTPRequestHandler.version_string(self).strip()
 
194
 
 
195
    def address_string(self):
 
196
        return self.client_address[0]
 
197
 
 
198
 
 
199
class BaseWSGIServer(HTTPServer):
 
200
    multithread = False
 
201
    multiprocess = False
 
202
 
 
203
    def __init__(self, host, port, app, handler=None,
 
204
                 passthrough_errors=False):
 
205
        if handler is None:
 
206
            handler = BaseRequestHandler
 
207
        HTTPServer.__init__(self, (host, int(port)), handler)
 
208
        self.app = app
 
209
        self.passthrough_errors = passthrough_errors
 
210
 
 
211
    def log(self, type, message, *args):
 
212
        _log(type, message, *args)
 
213
 
 
214
    def serve_forever(self):
 
215
        try:
 
216
            HTTPServer.serve_forever(self)
 
217
        except KeyboardInterrupt:
 
218
            pass
 
219
 
 
220
    def handle_error(self, request, client_address):
 
221
        if self.passthrough_errors:
 
222
            raise
 
223
        else:
 
224
            return HTTPServer.handle_error(self, request, client_address)
 
225
 
 
226
 
 
227
class ThreadedWSGIServer(ThreadingMixIn, BaseWSGIServer):
 
228
    multithread = True
 
229
 
 
230
 
 
231
class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer):
 
232
    multiprocess = True
 
233
 
 
234
    def __init__(self, host, port, app, processes=40, handler=None,
 
235
                 passthrough_errors=False):
 
236
        BaseWSGIServer.__init__(self, host, port, app, handler,
 
237
                                passthrough_errors)
 
238
        self.max_children = processes
 
239
 
 
240
 
 
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.
 
245
    """
 
246
    if threaded and processes > 1:
 
247
        raise ValueError("cannot have a multithreaded and "
 
248
                         "multi process server.")
 
249
    elif threaded:
 
250
        return ThreadedWSGIServer(host, port, app, request_handler,
 
251
                                  passthrough_errors)
 
252
    elif processes > 1:
 
253
        return ForkingWSGIServer(host, port, app, processes, request_handler,
 
254
                                 passthrough_errors)
 
255
    else:
 
256
        return BaseWSGIServer(host, port, app, request_handler,
 
257
                              passthrough_errors)
 
258
 
 
259
 
 
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.
 
263
 
 
264
    Copyright notice.  This function is based on the autoreload.py from
 
265
    the CherryPy trac which originated from WSGIKit which is now dead.
 
266
 
 
267
    :param extra_files: a list of additional files it should watch.
 
268
    """
 
269
    def iter_module_files():
 
270
        for module in sys.modules.values():
 
271
            filename = getattr(module, '__file__', None)
 
272
            if filename:
 
273
                while not os.path.isfile(filename):
 
274
                    filename = os.path.dirname(filename)
 
275
                    if not filename:
 
276
                        break
 
277
                else:
 
278
                    if filename[-4:] in ('.pyc', '.pyo'):
 
279
                        filename = filename[:-1]
 
280
                    yield filename
 
281
 
 
282
    mtimes = {}
 
283
    while 1:
 
284
        for filename in chain(iter_module_files(), extra_files or ()):
 
285
            try:
 
286
                mtime = os.stat(filename).st_mtime
 
287
            except OSError:
 
288
                continue
 
289
 
 
290
            old_time = mtimes.get(filename)
 
291
            if old_time is None:
 
292
                mtimes[filename] = mtime
 
293
                continue
 
294
            elif mtime > old_time:
 
295
                _log('info', ' * Detected change in %r, reloading' % filename)
 
296
                sys.exit(3)
 
297
        time.sleep(interval)
 
298
 
 
299
 
 
300
def restart_with_reloader():
 
301
    """Spawn a new Python interpreter with the same arguments as this one,
 
302
    but running the reloader thread.
 
303
    """
 
304
    while 1:
 
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)
 
310
        if exit_code != 3:
 
311
            return exit_code
 
312
 
 
313
 
 
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, ())
 
318
        try:
 
319
            reloader_loop(extra_files, interval)
 
320
        except KeyboardInterrupt:
 
321
            return
 
322
    try:
 
323
        sys.exit(restart_with_reloader())
 
324
    except KeyboardInterrupt:
 
325
        pass
 
326
 
 
327
 
 
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.
 
336
 
 
337
    .. versionadded:: 0.5
 
338
       `static_files` was added to simplify serving of static files as well
 
339
       as `passthrough_errors`.
 
340
 
 
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
 
350
                        files.
 
351
    :param reloader_interval: the interval for the reloader in seconds.
 
352
    :param threaded: should the process handle each request in a separate
 
353
                     thread?
 
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
 
357
                            with a different
 
358
                            :class:`~BaseHTTPServer.BaseHTTPRequestHandler`
 
359
                            subclass.
 
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
 
363
                         serving.
 
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.)
 
367
    """
 
368
    if use_debugger:
 
369
        from werkzeug.debug import DebuggedApplication
 
370
        application = DebuggedApplication(application, use_evalex)
 
371
    if static_files:
 
372
        from werkzeug.utils import SharedDataMiddleware
 
373
        application = SharedDataMiddleware(application, static_files)
 
374
 
 
375
    def inner():
 
376
        make_server(hostname, port, application, threaded,
 
377
                    processes, request_handler,
 
378
                    passthrough_errors).serve_forever()
 
379
 
 
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)
 
383
    if use_reloader:
 
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))
 
389
        test_socket.close()
 
390
        run_with_reloader(inner, extra_files, reloader_interval)
 
391
    else:
 
392
        inner()