Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/websdk/werkzeug/serving.py
diff options
context:
space:
mode:
Diffstat (limited to 'websdk/werkzeug/serving.py')
-rw-r--r--websdk/werkzeug/serving.py614
1 files changed, 614 insertions, 0 deletions
diff --git a/websdk/werkzeug/serving.py b/websdk/werkzeug/serving.py
new file mode 100644
index 0000000..48629f5
--- /dev/null
+++ b/websdk/werkzeug/serving.py
@@ -0,0 +1,614 @@
+# -*- coding: utf-8 -*-
+"""
+ werkzeug.serving
+ ~~~~~~~~~~~~~~~~
+
+ There are many ways to serve a WSGI application. While you're developing
+ it you usually don't want a full blown webserver like Apache but a simple
+ standalone one. From Python 2.5 onwards there is the `wsgiref`_ server in
+ the standard library. If you're using older versions of Python you can
+ download the package from the cheeseshop.
+
+ However there are some caveats. Sourcecode won't reload itself when
+ changed and each time you kill the server using ``^C`` you get an
+ `KeyboardInterrupt` error. While the latter is easy to solve the first
+ one can be a pain in the ass in some situations.
+
+ The easiest way is creating a small ``start-myproject.py`` that runs the
+ application::
+
+ #!/usr/bin/env python
+ # -*- coding: utf-8 -*-
+ from myproject import make_app
+ from werkzeug.serving import run_simple
+
+ app = make_app(...)
+ run_simple('localhost', 8080, app, use_reloader=True)
+
+ You can also pass it a `extra_files` keyword argument with a list of
+ additional files (like configuration files) you want to observe.
+
+ For bigger applications you should consider using `werkzeug.script`
+ instead of a simple start file.
+
+
+ :copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+import os
+import socket
+import sys
+import time
+import thread
+import signal
+import subprocess
+from urllib import unquote
+from SocketServer import ThreadingMixIn, ForkingMixIn
+from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
+
+import werkzeug
+from werkzeug._internal import _log
+from werkzeug.exceptions import InternalServerError
+
+
+class WSGIRequestHandler(BaseHTTPRequestHandler, object):
+ """A request handler that implements WSGI dispatching."""
+
+ @property
+ def server_version(self):
+ return 'Werkzeug/' + werkzeug.__version__
+
+ def make_environ(self):
+ if '?' in self.path:
+ path_info, query = self.path.split('?', 1)
+ else:
+ path_info = self.path
+ query = ''
+
+ def shutdown_server():
+ self.server.shutdown_signal = True
+
+ url_scheme = self.server.ssl_context is None and 'http' or 'https'
+ environ = {
+ 'wsgi.version': (1, 0),
+ 'wsgi.url_scheme': url_scheme,
+ 'wsgi.input': self.rfile,
+ 'wsgi.errors': sys.stderr,
+ 'wsgi.multithread': self.server.multithread,
+ 'wsgi.multiprocess': self.server.multiprocess,
+ 'wsgi.run_once': False,
+ 'werkzeug.server.shutdown':
+ shutdown_server,
+ 'SERVER_SOFTWARE': self.server_version,
+ 'REQUEST_METHOD': self.command,
+ 'SCRIPT_NAME': '',
+ 'PATH_INFO': unquote(path_info),
+ 'QUERY_STRING': query,
+ 'CONTENT_TYPE': self.headers.get('Content-Type', ''),
+ 'CONTENT_LENGTH': self.headers.get('Content-Length', ''),
+ 'REMOTE_ADDR': self.client_address[0],
+ 'REMOTE_PORT': self.client_address[1],
+ 'SERVER_NAME': self.server.server_address[0],
+ 'SERVER_PORT': str(self.server.server_address[1]),
+ 'SERVER_PROTOCOL': self.request_version
+ }
+
+ for key, value in self.headers.items():
+ key = 'HTTP_' + key.upper().replace('-', '_')
+ if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
+ environ[key] = value
+
+ return environ
+
+ def run_wsgi(self):
+ app = self.server.app
+ environ = self.make_environ()
+ headers_set = []
+ headers_sent = []
+
+ def write(data):
+ assert headers_set, 'write() before start_response'
+ if not headers_sent:
+ status, response_headers = headers_sent[:] = headers_set
+ code, msg = status.split(None, 1)
+ self.send_response(int(code), msg)
+ header_keys = set()
+ for key, value in response_headers:
+ self.send_header(key, value)
+ key = key.lower()
+ header_keys.add(key)
+ if 'content-length' not in header_keys:
+ self.close_connection = True
+ self.send_header('Connection', 'close')
+ if 'server' not in header_keys:
+ self.send_header('Server', self.version_string())
+ if 'date' not in header_keys:
+ self.send_header('Date', self.date_time_string())
+ self.end_headers()
+
+ assert type(data) is str, 'applications must write bytes'
+ self.wfile.write(data)
+ self.wfile.flush()
+
+ def start_response(status, response_headers, exc_info=None):
+ if exc_info:
+ try:
+ if headers_sent:
+ raise exc_info[0], exc_info[1], exc_info[2]
+ finally:
+ exc_info = None
+ elif headers_set:
+ raise AssertionError('Headers already set')
+ headers_set[:] = [status, response_headers]
+ return write
+
+ def execute(app):
+ application_iter = app(environ, start_response)
+ try:
+ for data in application_iter:
+ write(data)
+ # make sure the headers are sent
+ if not headers_sent:
+ write('')
+ finally:
+ if hasattr(application_iter, 'close'):
+ application_iter.close()
+ application_iter = None
+
+ try:
+ execute(app)
+ except (socket.error, socket.timeout), e:
+ self.connection_dropped(e, environ)
+ except Exception:
+ if self.server.passthrough_errors:
+ raise
+ from werkzeug.debug.tbtools import get_current_traceback
+ traceback = get_current_traceback(ignore_system_exceptions=True)
+ try:
+ # if we haven't yet sent the headers but they are set
+ # we roll back to be able to set them again.
+ if not headers_sent:
+ del headers_set[:]
+ execute(InternalServerError())
+ except Exception:
+ pass
+ self.server.log('error', 'Error on request:\n%s',
+ traceback.plaintext)
+
+ def handle(self):
+ """Handles a request ignoring dropped connections."""
+ try:
+ rv = BaseHTTPRequestHandler.handle(self)
+ except (socket.error, socket.timeout), e:
+ self.connection_dropped(e)
+ except Exception:
+ if self.server.ssl_context is None or not is_ssl_error():
+ raise
+ if self.server.shutdown_signal:
+ self.initiate_shutdown()
+ return rv
+
+ def initiate_shutdown(self):
+ """A horrible, horrible way to kill the server for Python 2.6 and
+ later. It's the best we can do.
+ """
+ # reloader active
+ if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
+ os.kill(os.getpid(), signal.SIGKILL)
+ # python 2.7
+ self.server._BaseServer__shutdown_request = True
+ # python 2.6
+ self.server._BaseServer__serving = False
+
+ def connection_dropped(self, error, environ=None):
+ """Called if the connection was closed by the client. By default
+ nothing happens.
+ """
+
+ def handle_one_request(self):
+ """Handle a single HTTP request."""
+ self.raw_requestline = self.rfile.readline()
+ if not self.raw_requestline:
+ self.close_connection = 1
+ elif self.parse_request():
+ return self.run_wsgi()
+
+ def send_response(self, code, message=None):
+ """Send the response header and log the response code."""
+ self.log_request(code)
+ if message is None:
+ message = code in self.responses and self.responses[code][0] or ''
+ if self.request_version != 'HTTP/0.9':
+ self.wfile.write("%s %d %s\r\n" %
+ (self.protocol_version, code, message))
+
+ def version_string(self):
+ return BaseHTTPRequestHandler.version_string(self).strip()
+
+ def address_string(self):
+ return self.client_address[0]
+
+ def log_request(self, code='-', size='-'):
+ self.log('info', '"%s" %s %s', self.requestline, code, size)
+
+ def log_error(self, *args):
+ self.log('error', *args)
+
+ def log_message(self, format, *args):
+ self.log('info', format, *args)
+
+ def log(self, type, message, *args):
+ _log(type, '%s - - [%s] %s\n' % (self.address_string(),
+ self.log_date_time_string(),
+ message % args))
+
+
+#: backwards compatible name if someone is subclassing it
+BaseRequestHandler = WSGIRequestHandler
+
+
+def generate_adhoc_ssl_context():
+ """Generates an adhoc SSL context for the development server."""
+ from random import random
+ from OpenSSL import crypto, SSL
+
+ cert = crypto.X509()
+ cert.set_serial_number(int(random() * sys.maxint))
+ cert.gmtime_adj_notBefore(0)
+ cert.gmtime_adj_notAfter(60 * 60 * 24 * 365)
+
+ subject = cert.get_subject()
+ subject.CN = '*'
+ subject.O = 'Dummy Certificate'
+
+ issuer = cert.get_issuer()
+ issuer.CN = 'Untrusted Authority'
+ issuer.O = 'Self-Signed'
+
+ pkey = crypto.PKey()
+ pkey.generate_key(crypto.TYPE_RSA, 768)
+ cert.set_pubkey(pkey)
+ cert.sign(pkey, 'md5')
+
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ ctx.use_privatekey(pkey)
+ ctx.use_certificate(cert)
+
+ return ctx
+
+
+def is_ssl_error(error=None):
+ """Checks if the given error (or the current one) is an SSL error."""
+ if error is None:
+ error = sys.exc_info()[1]
+ from OpenSSL import SSL
+ return isinstance(error, SSL.Error)
+
+
+class _SSLConnectionFix(object):
+ """Wrapper around SSL connection to provide a working makefile()."""
+
+ def __init__(self, con):
+ self._con = con
+
+ def makefile(self, mode, bufsize):
+ return socket._fileobject(self._con, mode, bufsize)
+
+ def __getattr__(self, attrib):
+ return getattr(self._con, attrib)
+
+
+def select_ip_version(host, port):
+ """Returns AF_INET4 or AF_INET6 depending on where to connect to."""
+ # disabled due to problems with current ipv6 implementations
+ # and various operating systems. Probably this code also is
+ # not supposed to work, but I can't come up with any other
+ # ways to implement this.
+ ##try:
+ ## info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
+ ## socket.SOCK_STREAM, 0,
+ ## socket.AI_PASSIVE)
+ ## if info:
+ ## return info[0][0]
+ ##except socket.gaierror:
+ ## pass
+ if ':' in host and hasattr(socket, 'AF_INET6'):
+ return socket.AF_INET6
+ return socket.AF_INET
+
+
+class BaseWSGIServer(HTTPServer, object):
+ """Simple single-threaded, single-process WSGI server."""
+ multithread = False
+ multiprocess = False
+ request_queue_size = 128
+
+ def __init__(self, host, port, app, handler=None,
+ passthrough_errors=False, ssl_context=None):
+ if handler is None:
+ handler = WSGIRequestHandler
+ self.address_family = select_ip_version(host, port)
+ HTTPServer.__init__(self, (host, int(port)), handler)
+ self.app = app
+ self.passthrough_errors = passthrough_errors
+ self.shutdown_signal = False
+
+ if ssl_context is not None:
+ try:
+ from OpenSSL import tsafe
+ except ImportError:
+ raise TypeError('SSL is not available if the OpenSSL '
+ 'library is not installed.')
+ if ssl_context == 'adhoc':
+ ssl_context = generate_adhoc_ssl_context()
+ self.socket = tsafe.Connection(ssl_context, self.socket)
+ self.ssl_context = ssl_context
+ else:
+ self.ssl_context = None
+
+ def log(self, type, message, *args):
+ _log(type, message, *args)
+
+ def serve_forever(self):
+ self.shutdown_signal = False
+ try:
+ HTTPServer.serve_forever(self)
+ except KeyboardInterrupt:
+ pass
+
+ def handle_error(self, request, client_address):
+ if self.passthrough_errors:
+ raise
+ else:
+ return HTTPServer.handle_error(self, request, client_address)
+
+ def get_request(self):
+ con, info = self.socket.accept()
+ if self.ssl_context is not None:
+ con = _SSLConnectionFix(con)
+ return con, info
+
+
+class ThreadedWSGIServer(ThreadingMixIn, BaseWSGIServer):
+ """A WSGI server that does threading."""
+ multithread = True
+
+
+class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer):
+ """A WSGI server that does forking."""
+ multiprocess = True
+
+ def __init__(self, host, port, app, processes=40, handler=None,
+ passthrough_errors=False, ssl_context=None):
+ BaseWSGIServer.__init__(self, host, port, app, handler,
+ passthrough_errors, ssl_context)
+ self.max_children = processes
+
+
+def make_server(host, port, app=None, threaded=False, processes=1,
+ request_handler=None, passthrough_errors=False,
+ ssl_context=None):
+ """Create a new server instance that is either threaded, or forks
+ or just processes one request after another.
+ """
+ if threaded and processes > 1:
+ raise ValueError("cannot have a multithreaded and "
+ "multi process server.")
+ elif threaded:
+ return ThreadedWSGIServer(host, port, app, request_handler,
+ passthrough_errors, ssl_context)
+ elif processes > 1:
+ return ForkingWSGIServer(host, port, app, processes, request_handler,
+ passthrough_errors, ssl_context)
+ else:
+ return BaseWSGIServer(host, port, app, request_handler,
+ passthrough_errors, ssl_context)
+
+
+def _iter_module_files():
+ for module in sys.modules.values():
+ filename = getattr(module, '__file__', None)
+ if filename:
+ old = None
+ while not os.path.isfile(filename):
+ old = filename
+ filename = os.path.dirname(filename)
+ if filename == old:
+ break
+ else:
+ if filename[-4:] in ('.pyc', '.pyo'):
+ filename = filename[:-1]
+ yield filename
+
+
+def _reloader_stat_loop(extra_files=None, interval=1):
+ """When this function is run from the main thread, it will force other
+ threads to exit when any modules currently loaded change.
+
+ Copyright notice. This function is based on the autoreload.py from
+ the CherryPy trac which originated from WSGIKit which is now dead.
+
+ :param extra_files: a list of additional files it should watch.
+ """
+ from itertools import chain
+ mtimes = {}
+ while 1:
+ for filename in chain(_iter_module_files(), extra_files or ()):
+ try:
+ mtime = os.stat(filename).st_mtime
+ except OSError:
+ continue
+
+ old_time = mtimes.get(filename)
+ if old_time is None:
+ mtimes[filename] = mtime
+ continue
+ elif mtime > old_time:
+ _log('info', ' * Detected change in %r, reloading' % filename)
+ sys.exit(3)
+ time.sleep(interval)
+
+
+def _reloader_inotify(extra_files=None, interval=None):
+ # Mutated by inotify loop when changes occur.
+ changed = [False]
+
+ # Setup inotify watches
+ from pyinotify import WatchManager, Notifier
+
+ # this API changed at one point, support both
+ try:
+ from pyinotify import EventsCodes as ec
+ ec.IN_ATTRIB
+ except (ImportError, AttributeError):
+ import pyinotify as ec
+
+ wm = WatchManager()
+ mask = ec.IN_DELETE_SELF | ec.IN_MOVE_SELF | ec.IN_MODIFY | ec.IN_ATTRIB
+
+ def signal_changed(event):
+ if changed[0]:
+ return
+ _log('info', ' * Detected change in %r, reloading' % event.path)
+ changed[:] = [True]
+
+ for fname in extra_files or ():
+ wm.add_watch(fname, mask, signal_changed)
+
+ # ... And now we wait...
+ notif = Notifier(wm)
+ try:
+ while not changed[0]:
+ # always reiterate through sys.modules, adding them
+ for fname in _iter_module_files():
+ wm.add_watch(fname, mask, signal_changed)
+ notif.process_events()
+ if notif.check_events(timeout=interval):
+ notif.read_events()
+ # TODO Set timeout to something small and check parent liveliness
+ finally:
+ notif.stop()
+ sys.exit(3)
+
+
+# currently we always use the stat loop reloader for the simple reason
+# that the inotify one does not respond to added files properly. Also
+# it's quite buggy and the API is a mess.
+reloader_loop = _reloader_stat_loop
+
+
+def restart_with_reloader():
+ """Spawn a new Python interpreter with the same arguments as this one,
+ but running the reloader thread.
+ """
+ while 1:
+ _log('info', ' * Restarting with reloader')
+ args = [sys.executable] + sys.argv
+ new_environ = os.environ.copy()
+ new_environ['WERKZEUG_RUN_MAIN'] = 'true'
+
+ # a weird bug on windows. sometimes unicode strings end up in the
+ # environment and subprocess.call does not like this, encode them
+ # to latin1 and continue.
+ if os.name == 'nt':
+ for key, value in new_environ.iteritems():
+ if isinstance(value, unicode):
+ new_environ[key] = value.encode('iso-8859-1')
+
+ exit_code = subprocess.call(args, env=new_environ)
+ if exit_code != 3:
+ return exit_code
+
+
+def run_with_reloader(main_func, extra_files=None, interval=1):
+ """Run the given function in an independent python interpreter."""
+ import signal
+ signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
+ if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
+ thread.start_new_thread(main_func, ())
+ try:
+ reloader_loop(extra_files, interval)
+ except KeyboardInterrupt:
+ return
+ try:
+ sys.exit(restart_with_reloader())
+ except KeyboardInterrupt:
+ pass
+
+
+def run_simple(hostname, port, application, use_reloader=False,
+ use_debugger=False, use_evalex=True,
+ extra_files=None, reloader_interval=1, threaded=False,
+ processes=1, request_handler=None, static_files=None,
+ passthrough_errors=False, ssl_context=None):
+ """Start an application using wsgiref and with an optional reloader. This
+ wraps `wsgiref` to fix the wrong default reporting of the multithreaded
+ WSGI variable and adds optional multithreading and fork support.
+
+ .. versionadded:: 0.5
+ `static_files` was added to simplify serving of static files as well
+ as `passthrough_errors`.
+
+ .. versionadded:: 0.6
+ support for SSL was added.
+
+ :param hostname: The host for the application. eg: ``'localhost'``
+ :param port: The port for the server. eg: ``8080``
+ :param application: the WSGI application to execute
+ :param use_reloader: should the server automatically restart the python
+ process if modules were changed?
+ :param use_debugger: should the werkzeug debugging system be used?
+ :param use_evalex: should the exception evaluation feature be enabled?
+ :param extra_files: a list of files the reloader should watch
+ additionally to the modules. For example configuration
+ files.
+ :param reloader_interval: the interval for the reloader in seconds.
+ :param threaded: should the process handle each request in a separate
+ thread?
+ :param processes: number of processes to spawn.
+ :param request_handler: optional parameter that can be used to replace
+ the default one. You can use this to replace it
+ with a different
+ :class:`~BaseHTTPServer.BaseHTTPRequestHandler`
+ subclass.
+ :param static_files: a dict of paths for static files. This works exactly
+ like :class:`SharedDataMiddleware`, it's actually
+ just wrapping the application in that middleware before
+ serving.
+ :param passthrough_errors: set this to `True` to disable the error catching.
+ This means that the server will die on errors but
+ it can be useful to hook debuggers in (pdb etc.)
+ :param ssl_context: an SSL context for the connection. Either an OpenSSL
+ context, the string ``'adhoc'`` if the server should
+ automatically create one, or `None` to disable SSL
+ (which is the default).
+ """
+ if use_debugger:
+ from werkzeug.debug import DebuggedApplication
+ application = DebuggedApplication(application, use_evalex)
+ if static_files:
+ from werkzeug.wsgi import SharedDataMiddleware
+ application = SharedDataMiddleware(application, static_files)
+
+ def inner():
+ make_server(hostname, port, application, threaded,
+ processes, request_handler,
+ passthrough_errors, ssl_context).serve_forever()
+
+ if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
+ display_hostname = hostname != '*' and hostname or 'localhost'
+ if ':' in display_hostname:
+ display_hostname = '[%s]' % display_hostname
+ _log('info', ' * Running on %s://%s:%d/', ssl_context is None
+ and 'http' or 'https', display_hostname, port)
+ if use_reloader:
+ # Create and destroy a socket so that any exceptions are raised before
+ # we spawn a separate Python interpreter and lose this ability.
+ address_family = select_ip_version(hostname, port)
+ test_socket = socket.socket(address_family, socket.SOCK_STREAM)
+ test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ test_socket.bind((hostname, port))
+ test_socket.close()
+ run_with_reloader(inner, extra_files, reloader_interval)
+ else:
+ inner()