123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077 |
- # -*- 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 `click`
- (http://click.pocoo.org) instead of a simple start file.
- :copyright: 2007 Pallets
- :license: BSD-3-Clause
- """
- import io
- import os
- import signal
- import socket
- import sys
- from ._compat import PY2
- from ._compat import reraise
- from ._compat import WIN
- from ._compat import wsgi_encoding_dance
- from ._internal import _log
- from .exceptions import InternalServerError
- from .urls import uri_to_iri
- from .urls import url_parse
- from .urls import url_unquote
- try:
- import socketserver
- from http.server import BaseHTTPRequestHandler
- from http.server import HTTPServer
- except ImportError:
- import SocketServer as socketserver
- from BaseHTTPServer import HTTPServer
- from BaseHTTPServer import BaseHTTPRequestHandler
- try:
- import ssl
- except ImportError:
- class _SslDummy(object):
- def __getattr__(self, name):
- raise RuntimeError("SSL support unavailable")
- ssl = _SslDummy()
- try:
- import termcolor
- except ImportError:
- termcolor = None
- def _get_openssl_crypto_module():
- try:
- from OpenSSL import crypto
- except ImportError:
- raise TypeError("Using ad-hoc certificates requires the pyOpenSSL library.")
- else:
- return crypto
- ThreadingMixIn = socketserver.ThreadingMixIn
- can_fork = hasattr(os, "fork")
- if can_fork:
- ForkingMixIn = socketserver.ForkingMixIn
- else:
- class ForkingMixIn(object):
- pass
- try:
- af_unix = socket.AF_UNIX
- except AttributeError:
- af_unix = None
- LISTEN_QUEUE = 128
- can_open_by_fd = not WIN and hasattr(socket, "fromfd")
- # On Python 3, ConnectionError represents the same errnos as
- # socket.error from Python 2, while socket.error is an alias for the
- # more generic OSError.
- if PY2:
- _ConnectionError = socket.error
- else:
- _ConnectionError = ConnectionError
- class DechunkedInput(io.RawIOBase):
- """An input stream that handles Transfer-Encoding 'chunked'"""
- def __init__(self, rfile):
- self._rfile = rfile
- self._done = False
- self._len = 0
- def readable(self):
- return True
- def read_chunk_len(self):
- try:
- line = self._rfile.readline().decode("latin1")
- _len = int(line.strip(), 16)
- except ValueError:
- raise IOError("Invalid chunk header")
- if _len < 0:
- raise IOError("Negative chunk length not allowed")
- return _len
- def readinto(self, buf):
- read = 0
- while not self._done and read < len(buf):
- if self._len == 0:
- # This is the first chunk or we fully consumed the previous
- # one. Read the next length of the next chunk
- self._len = self.read_chunk_len()
- if self._len == 0:
- # Found the final chunk of size 0. The stream is now exhausted,
- # but there is still a final newline that should be consumed
- self._done = True
- if self._len > 0:
- # There is data (left) in this chunk, so append it to the
- # buffer. If this operation fully consumes the chunk, this will
- # reset self._len to 0.
- n = min(len(buf), self._len)
- buf[read : read + n] = self._rfile.read(n)
- self._len -= n
- read += n
- if self._len == 0:
- # Skip the terminating newline of a chunk that has been fully
- # consumed. This also applies to the 0-sized final chunk
- terminator = self._rfile.readline()
- if terminator not in (b"\n", b"\r\n", b"\r"):
- raise IOError("Missing chunk terminating newline")
- return read
- class WSGIRequestHandler(BaseHTTPRequestHandler, object):
- """A request handler that implements WSGI dispatching."""
- @property
- def server_version(self):
- from . import __version__
- return "Werkzeug/" + __version__
- def make_environ(self):
- request_url = url_parse(self.path)
- def shutdown_server():
- self.server.shutdown_signal = True
- url_scheme = "http" if self.server.ssl_context is None else "https"
- if not self.client_address:
- self.client_address = "<local>"
- if isinstance(self.client_address, str):
- self.client_address = (self.client_address, 0)
- else:
- pass
- path_info = url_unquote(request_url.path)
- 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": wsgi_encoding_dance(path_info),
- "QUERY_STRING": wsgi_encoding_dance(request_url.query),
- # Non-standard, added by mod_wsgi, uWSGI
- "REQUEST_URI": wsgi_encoding_dance(self.path),
- # Non-standard, added by gunicorn
- "RAW_URI": wsgi_encoding_dance(self.path),
- "REMOTE_ADDR": self.address_string(),
- "REMOTE_PORT": self.port_integer(),
- "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.get_header_items():
- key = key.upper().replace("-", "_")
- value = value.replace("\r\n", "")
- if key not in ("CONTENT_TYPE", "CONTENT_LENGTH"):
- key = "HTTP_" + key
- if key in environ:
- value = "{},{}".format(environ[key], value)
- environ[key] = value
- if environ.get("HTTP_TRANSFER_ENCODING", "").strip().lower() == "chunked":
- environ["wsgi.input_terminated"] = True
- environ["wsgi.input"] = DechunkedInput(environ["wsgi.input"])
- if request_url.scheme and request_url.netloc:
- environ["HTTP_HOST"] = request_url.netloc
- return environ
- def run_wsgi(self):
- if self.headers.get("Expect", "").lower().strip() == "100-continue":
- self.wfile.write(b"HTTP/1.1 100 Continue\r\n\r\n")
- self.environ = 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
- try:
- code, msg = status.split(None, 1)
- except ValueError:
- code, msg = status, ""
- code = int(code)
- self.send_response(code, msg)
- header_keys = set()
- for key, value in response_headers:
- self.send_header(key, value)
- key = key.lower()
- header_keys.add(key)
- if not (
- "content-length" in header_keys
- or environ["REQUEST_METHOD"] == "HEAD"
- or code < 200
- or code in (204, 304)
- ):
- 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 isinstance(data, bytes), "applications must write bytes"
- if data:
- # Only write data if there is any to avoid Python 3.5 SSL bug
- self.wfile.write(data)
- self.wfile.flush()
- def start_response(status, response_headers, exc_info=None):
- if exc_info:
- try:
- if headers_sent:
- reraise(*exc_info)
- 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)
- if not headers_sent:
- write(b"")
- finally:
- if hasattr(application_iter, "close"):
- application_iter.close()
- application_iter = None
- try:
- execute(self.server.app)
- except (_ConnectionError, socket.timeout) as e:
- self.connection_dropped(e, environ)
- except Exception:
- if self.server.passthrough_errors:
- raise
- from .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."""
- rv = None
- try:
- rv = BaseHTTPRequestHandler.handle(self)
- except (_ConnectionError, socket.timeout) as e:
- self.connection_dropped(e)
- except Exception as e:
- if self.server.ssl_context is None or not is_ssl_error(e):
- 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.
- """
- # Windows does not provide SIGKILL, go with SIGTERM then.
- sig = getattr(signal, "SIGKILL", signal.SIGTERM)
- # reloader active
- if is_running_from_reloader():
- os.kill(os.getpid(), sig)
- # 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":
- hdr = "%s %d %s\r\n" % (self.protocol_version, code, message)
- self.wfile.write(hdr.encode("ascii"))
- def version_string(self):
- return BaseHTTPRequestHandler.version_string(self).strip()
- def address_string(self):
- if getattr(self, "environ", None):
- return self.environ["REMOTE_ADDR"]
- elif not self.client_address:
- return "<local>"
- elif isinstance(self.client_address, str):
- return self.client_address
- else:
- return self.client_address[0]
- def port_integer(self):
- return self.client_address[1]
- def log_request(self, code="-", size="-"):
- try:
- path = uri_to_iri(self.path)
- msg = "%s %s %s" % (self.command, path, self.request_version)
- except AttributeError:
- # path isn't set if the requestline was bad
- msg = self.requestline
- code = str(code)
- if termcolor:
- color = termcolor.colored
- if code[0] == "1": # 1xx - Informational
- msg = color(msg, attrs=["bold"])
- elif code[0] == "2": # 2xx - Success
- msg = color(msg, color="white")
- elif code == "304": # 304 - Resource Not Modified
- msg = color(msg, color="cyan")
- elif code[0] == "3": # 3xx - Redirection
- msg = color(msg, color="green")
- elif code == "404": # 404 - Resource Not Found
- msg = color(msg, color="yellow")
- elif code[0] == "4": # 4xx - Client Error
- msg = color(msg, color="red", attrs=["bold"])
- else: # 5xx, or any other response
- msg = color(msg, color="magenta", attrs=["bold"])
- self.log("info", '"%s" %s %s', msg, 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),
- )
- def get_header_items(self):
- """
- Get an iterable list of key/value pairs representing headers.
- This function provides Python 2/3 compatibility as related to the
- parsing of request headers. Python 2.7 is not compliant with
- RFC 3875 Section 4.1.18 which requires multiple values for headers
- to be provided or RFC 2616 which allows for folding of multi-line
- headers. This function will return a matching list regardless
- of Python version. It can be removed once Python 2.7 support
- is dropped.
- :return: List of tuples containing header hey/value pairs
- """
- if PY2:
- # For Python 2, process the headers manually according to
- # W3C RFC 2616 Section 4.2.
- items = []
- for header in self.headers.headers:
- # Remove "\r\n" from the header and split on ":" to get
- # the field name and value.
- try:
- key, value = header[0:-2].split(":", 1)
- except ValueError:
- # If header could not be slit with : but starts with white
- # space and it follows an existing header, it's a folded
- # header.
- if header[0] in ("\t", " ") and items:
- # Pop off the last header
- key, value = items.pop()
- # Append the current header to the value of the last
- # header which will be placed back on the end of the
- # list
- value = value + header
- # Otherwise it's just a bad header and should error
- else:
- # Re-raise the value error
- raise
- # Add the key and the value once stripped of leading
- # white space. The specification allows for stripping
- # trailing white space but the Python 3 code does not
- # strip trailing white space. Therefore, trailing space
- # will be left as is to match the Python 3 behavior.
- items.append((key, value.lstrip()))
- else:
- items = self.headers.items()
- return items
- #: backwards compatible name if someone is subclassing it
- BaseRequestHandler = WSGIRequestHandler
- def generate_adhoc_ssl_pair(cn=None):
- from random import random
- crypto = _get_openssl_crypto_module()
- # pretty damn sure that this is not actually accepted by anyone
- if cn is None:
- cn = "*"
- cert = crypto.X509()
- cert.set_serial_number(int(random() * sys.maxsize))
- cert.gmtime_adj_notBefore(0)
- cert.gmtime_adj_notAfter(60 * 60 * 24 * 365)
- subject = cert.get_subject()
- subject.CN = cn
- subject.O = "Dummy Certificate" # noqa: E741
- issuer = cert.get_issuer()
- issuer.CN = subject.CN
- issuer.O = subject.O # noqa: E741
- pkey = crypto.PKey()
- pkey.generate_key(crypto.TYPE_RSA, 2048)
- cert.set_pubkey(pkey)
- cert.sign(pkey, "sha256")
- return cert, pkey
- def make_ssl_devcert(base_path, host=None, cn=None):
- """Creates an SSL key for development. This should be used instead of
- the ``'adhoc'`` key which generates a new cert on each server start.
- It accepts a path for where it should store the key and cert and
- either a host or CN. If a host is given it will use the CN
- ``*.host/CN=host``.
- For more information see :func:`run_simple`.
- .. versionadded:: 0.9
- :param base_path: the path to the certificate and key. The extension
- ``.crt`` is added for the certificate, ``.key`` is
- added for the key.
- :param host: the name of the host. This can be used as an alternative
- for the `cn`.
- :param cn: the `CN` to use.
- """
- from OpenSSL import crypto
- if host is not None:
- cn = "*.%s/CN=%s" % (host, host)
- cert, pkey = generate_adhoc_ssl_pair(cn=cn)
- cert_file = base_path + ".crt"
- pkey_file = base_path + ".key"
- with open(cert_file, "wb") as f:
- f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
- with open(pkey_file, "wb") as f:
- f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
- return cert_file, pkey_file
- def generate_adhoc_ssl_context():
- """Generates an adhoc SSL context for the development server."""
- crypto = _get_openssl_crypto_module()
- import tempfile
- import atexit
- cert, pkey = generate_adhoc_ssl_pair()
- cert_handle, cert_file = tempfile.mkstemp()
- pkey_handle, pkey_file = tempfile.mkstemp()
- atexit.register(os.remove, pkey_file)
- atexit.register(os.remove, cert_file)
- os.write(cert_handle, crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
- os.write(pkey_handle, crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
- os.close(cert_handle)
- os.close(pkey_handle)
- ctx = load_ssl_context(cert_file, pkey_file)
- return ctx
- def load_ssl_context(cert_file, pkey_file=None, protocol=None):
- """Loads SSL context from cert/private key files and optional protocol.
- Many parameters are directly taken from the API of
- :py:class:`ssl.SSLContext`.
- :param cert_file: Path of the certificate to use.
- :param pkey_file: Path of the private key to use. If not given, the key
- will be obtained from the certificate file.
- :param protocol: One of the ``PROTOCOL_*`` constants in the stdlib ``ssl``
- module. Defaults to ``PROTOCOL_SSLv23``.
- """
- if protocol is None:
- protocol = ssl.PROTOCOL_SSLv23
- ctx = _SSLContext(protocol)
- ctx.load_cert_chain(cert_file, pkey_file)
- return ctx
- class _SSLContext(object):
- """A dummy class with a small subset of Python3's ``ssl.SSLContext``, only
- intended to be used with and by Werkzeug."""
- def __init__(self, protocol):
- self._protocol = protocol
- self._certfile = None
- self._keyfile = None
- self._password = None
- def load_cert_chain(self, certfile, keyfile=None, password=None):
- self._certfile = certfile
- self._keyfile = keyfile or certfile
- self._password = password
- def wrap_socket(self, sock, **kwargs):
- return ssl.wrap_socket(
- sock,
- keyfile=self._keyfile,
- certfile=self._certfile,
- ssl_version=self._protocol,
- **kwargs
- )
- def is_ssl_error(error=None):
- """Checks if the given error (or the current one) is an SSL error."""
- exc_types = (ssl.SSLError,)
- try:
- from OpenSSL.SSL import Error
- exc_types += (Error,)
- except ImportError:
- pass
- if error is None:
- error = sys.exc_info()[1]
- return isinstance(error, exc_types)
- def select_address_family(host, port):
- """Return ``AF_INET4``, ``AF_INET6``, or ``AF_UNIX`` depending on
- the host and port."""
- # 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 host.startswith("unix://"):
- return socket.AF_UNIX
- elif ":" in host and hasattr(socket, "AF_INET6"):
- return socket.AF_INET6
- return socket.AF_INET
- def get_sockaddr(host, port, family):
- """Return a fully qualified socket address that can be passed to
- :func:`socket.bind`."""
- if family == af_unix:
- return host.split("://", 1)[1]
- try:
- res = socket.getaddrinfo(
- host, port, family, socket.SOCK_STREAM, socket.IPPROTO_TCP
- )
- except socket.gaierror:
- return host, port
- return res[0][4]
- class BaseWSGIServer(HTTPServer, object):
- """Simple single-threaded, single-process WSGI server."""
- multithread = False
- multiprocess = False
- request_queue_size = LISTEN_QUEUE
- def __init__(
- self,
- host,
- port,
- app,
- handler=None,
- passthrough_errors=False,
- ssl_context=None,
- fd=None,
- ):
- if handler is None:
- handler = WSGIRequestHandler
- self.address_family = select_address_family(host, port)
- if fd is not None:
- real_sock = socket.fromfd(fd, self.address_family, socket.SOCK_STREAM)
- port = 0
- server_address = get_sockaddr(host, int(port), self.address_family)
- # remove socket file if it already exists
- if self.address_family == af_unix and os.path.exists(server_address):
- os.unlink(server_address)
- HTTPServer.__init__(self, server_address, handler)
- self.app = app
- self.passthrough_errors = passthrough_errors
- self.shutdown_signal = False
- self.host = host
- self.port = self.socket.getsockname()[1]
- # Patch in the original socket.
- if fd is not None:
- self.socket.close()
- self.socket = real_sock
- self.server_address = self.socket.getsockname()
- if ssl_context is not None:
- if isinstance(ssl_context, tuple):
- ssl_context = load_ssl_context(*ssl_context)
- if ssl_context == "adhoc":
- ssl_context = generate_adhoc_ssl_context()
- # If we are on Python 2 the return value from socket.fromfd
- # is an internal socket object but what we need for ssl wrap
- # is the wrapper around it :(
- sock = self.socket
- if PY2 and not isinstance(sock, socket.socket):
- sock = socket.socket(sock.family, sock.type, sock.proto, sock)
- self.socket = ssl_context.wrap_socket(sock, server_side=True)
- 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
- finally:
- self.server_close()
- def handle_error(self, request, client_address):
- if self.passthrough_errors:
- raise
- # Python 2 still causes a socket.error after the earlier
- # handling, so silence it here.
- if isinstance(sys.exc_info()[1], _ConnectionError):
- return
- return HTTPServer.handle_error(self, request, client_address)
- def get_request(self):
- con, info = self.socket.accept()
- return con, info
- class ThreadedWSGIServer(ThreadingMixIn, BaseWSGIServer):
- """A WSGI server that does threading."""
- multithread = True
- daemon_threads = 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,
- fd=None,
- ):
- if not can_fork:
- raise ValueError("Your platform does not support forking.")
- BaseWSGIServer.__init__(
- self, host, port, app, handler, passthrough_errors, ssl_context, fd
- )
- self.max_children = processes
- def make_server(
- host=None,
- port=None,
- app=None,
- threaded=False,
- processes=1,
- request_handler=None,
- passthrough_errors=False,
- ssl_context=None,
- fd=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, fd=fd
- )
- elif processes > 1:
- return ForkingWSGIServer(
- host,
- port,
- app,
- processes,
- request_handler,
- passthrough_errors,
- ssl_context,
- fd=fd,
- )
- else:
- return BaseWSGIServer(
- host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd
- )
- def is_running_from_reloader():
- """Checks if the application is running from within the Werkzeug
- reloader subprocess.
- .. versionadded:: 0.10
- """
- return os.environ.get("WERKZEUG_RUN_MAIN") == "true"
- def run_simple(
- hostname,
- port,
- application,
- use_reloader=False,
- use_debugger=False,
- use_evalex=True,
- extra_files=None,
- reloader_interval=1,
- reloader_type="auto",
- threaded=False,
- processes=1,
- request_handler=None,
- static_files=None,
- passthrough_errors=False,
- ssl_context=None,
- ):
- """Start a WSGI application. Optional features include a reloader,
- multithreading and fork support.
- This function has a command-line interface too::
- python -m werkzeug.serving --help
- .. 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.
- .. versionadded:: 0.8
- Added support for automatically loading a SSL context from certificate
- file and private key.
- .. versionadded:: 0.9
- Added command-line interface.
- .. versionadded:: 0.10
- Improved the reloader and added support for changing the backend
- through the `reloader_type` parameter. See :ref:`reloader`
- for more information.
- .. versionchanged:: 0.15
- Bind to a Unix socket by passing a path that starts with
- ``unix://`` as the ``hostname``.
- :param hostname: The host to bind to, for example ``'localhost'``.
- If the value is a path that starts with ``unix://`` it will bind
- to a Unix socket instead of a TCP socket..
- :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 reloader_type: the type of reloader to use. The default is
- auto detection. Valid values are ``'stat'`` and
- ``'watchdog'``. See :ref:`reloader` for more
- information.
- :param threaded: should the process handle each request in a separate
- thread?
- :param processes: if greater than 1 then handle each request in a new process
- up to this maximum number of concurrent processes.
- :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 list or 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
- :class:`ssl.SSLContext`, a tuple in the form
- ``(cert_file, pkey_file)``, the string ``'adhoc'`` if
- the server should automatically create one, or ``None``
- to disable SSL (which is the default).
- """
- if not isinstance(port, int):
- raise TypeError("port must be an integer")
- if use_debugger:
- from .debug import DebuggedApplication
- application = DebuggedApplication(application, use_evalex)
- if static_files:
- from .middleware.shared_data import SharedDataMiddleware
- application = SharedDataMiddleware(application, static_files)
- def log_startup(sock):
- display_hostname = hostname if hostname not in ("", "*") else "localhost"
- quit_msg = "(Press CTRL+C to quit)"
- if sock.family == af_unix:
- _log("info", " * Running on %s %s", display_hostname, quit_msg)
- else:
- if ":" in display_hostname:
- display_hostname = "[%s]" % display_hostname
- port = sock.getsockname()[1]
- _log(
- "info",
- " * Running on %s://%s:%d/ %s",
- "http" if ssl_context is None else "https",
- display_hostname,
- port,
- quit_msg,
- )
- def inner():
- try:
- fd = int(os.environ["WERKZEUG_SERVER_FD"])
- except (LookupError, ValueError):
- fd = None
- srv = make_server(
- hostname,
- port,
- application,
- threaded,
- processes,
- request_handler,
- passthrough_errors,
- ssl_context,
- fd=fd,
- )
- if fd is None:
- log_startup(srv.socket)
- srv.serve_forever()
- if use_reloader:
- # If we're not running already in the subprocess that is the
- # reloader we want to open up a socket early to make sure the
- # port is actually available.
- if not is_running_from_reloader():
- if port == 0 and not can_open_by_fd:
- raise ValueError(
- "Cannot bind to a random port with enabled "
- "reloader if the Python interpreter does "
- "not support socket opening by fd."
- )
- # 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_address_family(hostname, port)
- server_address = get_sockaddr(hostname, port, address_family)
- s = socket.socket(address_family, socket.SOCK_STREAM)
- s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- s.bind(server_address)
- if hasattr(s, "set_inheritable"):
- s.set_inheritable(True)
- # If we can open the socket by file descriptor, then we can just
- # reuse this one and our socket will survive the restarts.
- if can_open_by_fd:
- os.environ["WERKZEUG_SERVER_FD"] = str(s.fileno())
- s.listen(LISTEN_QUEUE)
- log_startup(s)
- else:
- s.close()
- if address_family == af_unix:
- _log("info", "Unlinking %s" % server_address)
- os.unlink(server_address)
- # Do not use relative imports, otherwise "python -m werkzeug.serving"
- # breaks.
- from ._reloader import run_with_reloader
- run_with_reloader(inner, extra_files, reloader_interval, reloader_type)
- else:
- inner()
- def run_with_reloader(*args, **kwargs):
- # People keep using undocumented APIs. Do not use this function
- # please, we do not guarantee that it continues working.
- from ._reloader import run_with_reloader
- return run_with_reloader(*args, **kwargs)
- def main():
- """A simple command-line interface for :py:func:`run_simple`."""
- # in contrast to argparse, this works at least under Python < 2.7
- import optparse
- from .utils import import_string
- parser = optparse.OptionParser(usage="Usage: %prog [options] app_module:app_object")
- parser.add_option(
- "-b",
- "--bind",
- dest="address",
- help="The hostname:port the app should listen on.",
- )
- parser.add_option(
- "-d",
- "--debug",
- dest="use_debugger",
- action="store_true",
- default=False,
- help="Use Werkzeug's debugger.",
- )
- parser.add_option(
- "-r",
- "--reload",
- dest="use_reloader",
- action="store_true",
- default=False,
- help="Reload Python process if modules change.",
- )
- options, args = parser.parse_args()
- hostname, port = None, None
- if options.address:
- address = options.address.split(":")
- hostname = address[0]
- if len(address) > 1:
- port = address[1]
- if len(args) != 1:
- sys.stdout.write("No application supplied, or too much. See --help\n")
- sys.exit(1)
- app = import_string(args[0])
- run_simple(
- hostname=(hostname or "127.0.0.1"),
- port=int(port or 5000),
- application=app,
- use_reloader=options.use_reloader,
- use_debugger=options.use_debugger,
- )
- if __name__ == "__main__":
- main()
|