Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/werkzeug/serving.py: 18%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""A WSGI and HTTP server for use **during development only**. This
2server is convenient to use, but is not designed to be particularly
3stable, secure, or efficient. Use a dedicate WSGI server and HTTP
4server when deploying to production.
6It provides features like interactive debugging and code reloading. Use
7``run_simple`` to start the server. Put this in a ``run.py`` script:
9.. code-block:: python
11 from myapp import create_app
12 from werkzeug import run_simple
13"""
15from __future__ import annotations
17import contextlib
18import datetime as dt
19import errno
20import io
21import os
22import selectors
23import socket
24import socketserver
25import sys
26import typing as t
27import wsgiref.types as wt
28from http.server import BaseHTTPRequestHandler
29from http.server import HTTPServer
30from types import TracebackType
31from urllib.parse import unquote
32from urllib.parse import urlsplit
34from ._internal import _log
35from ._internal import _plain_int
36from ._internal import _wsgi_encoding_dance
37from .datastructures.set import HeaderSet
38from .exceptions import InternalServerError
39from .urls import uri_to_iri
41try:
42 import ssl
44 connection_dropped_errors: tuple[type[Exception], ...] = (
45 ConnectionError,
46 socket.timeout,
47 ssl.SSLEOFError,
48 )
49except ImportError:
51 class _SslDummy:
52 def __getattr__(self, name: str) -> t.Any:
53 raise RuntimeError( # noqa: B904
54 "SSL is unavailable because this Python runtime was not"
55 " compiled with SSL/TLS support."
56 )
58 ssl = _SslDummy() # type: ignore[assignment]
59 connection_dropped_errors = (ConnectionError, socket.timeout)
61can_fork = hasattr(os, "fork")
63if can_fork:
64 ForkingMixIn = socketserver.ForkingMixIn
65else:
67 class ForkingMixIn: # type: ignore[no-redef]
68 pass
71try:
72 af_unix: int | None = socket.AF_UNIX
73except AttributeError:
74 af_unix = None
76LISTEN_QUEUE = 128
78_TSSLContextArg = t.Union[
79 "ssl.SSLContext", tuple[str, str | None], t.Literal["adhoc"], None
80]
82if t.TYPE_CHECKING:
83 from cryptography.hazmat.primitives.asymmetric.rsa import (
84 RSAPrivateKeyWithSerialization,
85 )
86 from cryptography.x509 import Certificate
89class DechunkedInput(io.RawIOBase):
90 """An input stream that handles ``Transfer-Encoding: chunked``. Only
91 used by the dev server, which must not be used in production. A production
92 WSGI server will have its own robust, secure chunk handler.
93 """
95 def __init__(self, rfile: t.IO[bytes]) -> None:
96 self._rfile = rfile
97 self._done = False
98 self._len = 0
100 def readable(self) -> bool:
101 return True
103 def read_chunk_len(self) -> int:
104 try:
105 line = self._rfile.readline(100).decode("latin1").removesuffix("\r\n")
106 _len = _plain_int(line, 16)
107 except ValueError as e:
108 raise OSError("Invalid chunk header") from e
109 if _len < 0:
110 raise OSError("Negative chunk length not allowed")
111 return _len
113 def readinto(self, buf: bytearray) -> int: # type: ignore[override]
114 read = 0
115 while not self._done and read < len(buf):
116 if self._len == 0:
117 # This is the first chunk or we fully consumed the previous
118 # one. Read the next length of the next chunk
119 self._len = self.read_chunk_len()
121 if self._len == 0:
122 # Found the final chunk of size 0. The stream is now exhausted,
123 # but there is still a final newline that should be consumed
124 self._done = True
126 if self._len > 0:
127 # There is data (left) in this chunk, so append it to the
128 # buffer. If this operation fully consumes the chunk, this will
129 # reset self._len to 0.
130 n = min(len(buf), self._len)
132 # If (read + chunk size) becomes more than len(buf), buf will
133 # grow beyond the original size and read more data than
134 # required. So only read as much data as can fit in buf.
135 if read + n > len(buf):
136 buf[read:] = self._rfile.read(len(buf) - read)
137 self._len -= len(buf) - read
138 read = len(buf)
139 else:
140 buf[read : read + n] = self._rfile.read(n)
141 self._len -= n
142 read += n
144 if self._len == 0:
145 # Skip the terminating newline of a chunk that has been fully
146 # consumed. This also applies to the 0-sized final chunk
147 terminator = self._rfile.readline(2)
148 if terminator not in (b"\n", b"\r\n", b"\r"):
149 raise OSError("Missing chunk terminating newline")
151 return read
154class WSGIRequestHandler(BaseHTTPRequestHandler):
155 """A request handler that implements WSGI dispatching."""
157 server: BaseWSGIServer
159 @property
160 def server_version(self) -> str: # type: ignore[override]
161 return self.server._server_version
163 def make_environ(self) -> wt.WSGIEnvironment:
164 request_url = urlsplit(self.path)
165 url_scheme = "http" if self.server.ssl_context is None else "https"
167 if not self.client_address:
168 self.client_address = ("<local>", 0)
169 elif isinstance(self.client_address, str):
170 self.client_address = (self.client_address, 0)
172 # If there was no scheme but the path started with two slashes,
173 # the first segment may have been incorrectly parsed as the
174 # netloc, prepend it to the path again.
175 if not request_url.scheme and request_url.netloc:
176 path_info = f"/{request_url.netloc}{request_url.path}"
177 else:
178 path_info = request_url.path
180 path_info = unquote(path_info)
182 environ: wt.WSGIEnvironment = {
183 "wsgi.version": (1, 0),
184 "wsgi.url_scheme": url_scheme,
185 "wsgi.input": self.rfile,
186 "wsgi.errors": sys.stderr,
187 "wsgi.multithread": self.server.multithread,
188 "wsgi.multiprocess": self.server.multiprocess,
189 "wsgi.run_once": False,
190 "werkzeug.socket": self.connection,
191 "SERVER_SOFTWARE": self.server_version,
192 "REQUEST_METHOD": self.command,
193 "SCRIPT_NAME": "",
194 "PATH_INFO": _wsgi_encoding_dance(path_info),
195 "QUERY_STRING": _wsgi_encoding_dance(request_url.query),
196 # Non-standard, added by mod_wsgi, uWSGI
197 "REQUEST_URI": _wsgi_encoding_dance(self.path),
198 # Non-standard, added by gunicorn
199 "RAW_URI": _wsgi_encoding_dance(self.path),
200 "REMOTE_ADDR": self.address_string(),
201 "REMOTE_PORT": self.port_integer(),
202 "SERVER_NAME": self.server.server_address[0],
203 "SERVER_PORT": str(self.server.server_address[1]),
204 "SERVER_PROTOCOL": self.request_version,
205 }
207 for key, value in self.headers.items():
208 if "_" in key:
209 continue
211 key = key.upper().replace("-", "_")
212 value = value.replace("\r\n", "")
213 if key not in ("CONTENT_TYPE", "CONTENT_LENGTH"):
214 key = f"HTTP_{key}"
215 if key in environ:
216 value = f"{environ[key]},{value}"
217 environ[key] = value
219 if "chunked" in HeaderSet.from_header(environ.get("HTTP_TRANSFER_ENCODING")):
220 environ["wsgi.input_terminated"] = True
221 environ["wsgi.input"] = DechunkedInput(environ["wsgi.input"])
223 # Per RFC 2616, if the URL is absolute, use that as the host.
224 # We're using "has a scheme" to indicate an absolute URL.
225 if request_url.scheme and request_url.netloc:
226 environ["HTTP_HOST"] = request_url.netloc
228 try:
229 # binary_form=False gives nicer information, but wouldn't be compatible with
230 # what Nginx or Apache could return.
231 peer_cert = self.connection.getpeercert(binary_form=True)
232 if peer_cert is not None:
233 # Nginx and Apache use PEM format.
234 environ["SSL_CLIENT_CERT"] = ssl.DER_cert_to_PEM_cert(peer_cert)
235 except ValueError:
236 # SSL handshake hasn't finished.
237 self.server.log("error", "Cannot fetch SSL peer certificate info")
238 except AttributeError:
239 # Not using TLS, the socket will not have getpeercert().
240 pass
242 return environ
244 def run_wsgi(self) -> None:
245 self.environ = environ = self.make_environ()
246 status_set: str | None = None
247 headers_set: list[tuple[str, str]] | None = None
248 status_sent: str | None = None
249 headers_sent: list[tuple[str, str]] | None = None
250 chunk_response: bool = False
252 def write(data: bytes) -> None:
253 nonlocal status_sent, headers_sent, chunk_response
254 assert status_set is not None, "write() before start_response"
255 assert headers_set is not None, "write() before start_response"
256 if status_sent is None:
257 status_sent = status_set
258 headers_sent = headers_set
259 try:
260 code_str, msg = status_sent.split(None, 1)
261 except ValueError:
262 code_str, msg = status_sent, ""
263 code = int(code_str)
264 self.send_response(code, msg)
265 header_keys = set()
266 for key, value in headers_sent:
267 self.send_header(key, value)
268 header_keys.add(key.lower())
270 # Use chunked transfer encoding if there is no content
271 # length. Do not use for 1xx and 204 responses. 304
272 # responses and HEAD requests are also excluded, which
273 # is the more conservative behavior and matches other
274 # parts of the code.
275 # https://httpwg.org/specs/rfc7230.html#rfc.section.3.3.1
276 if (
277 not (
278 "content-length" in header_keys
279 or environ["REQUEST_METHOD"] == "HEAD"
280 or (100 <= code < 200)
281 or code in {204, 304}
282 )
283 and self.protocol_version >= "HTTP/1.1"
284 ):
285 chunk_response = True
286 self.send_header("Transfer-Encoding", "chunked")
288 # Always close the connection. This disables HTTP/1.1
289 # keep-alive connections. They aren't handled well by
290 # Python's http.server because it doesn't know how to
291 # drain the stream before the next request line.
292 self.send_header("Connection", "close")
293 self.end_headers()
295 assert isinstance(data, bytes), "applications must write bytes"
297 if data:
298 if chunk_response:
299 self.wfile.write(hex(len(data))[2:].encode())
300 self.wfile.write(b"\r\n")
302 self.wfile.write(data)
304 if chunk_response:
305 self.wfile.write(b"\r\n")
307 self.wfile.flush()
309 def start_response(
310 status: str,
311 headers: list[tuple[str, str]],
312 exc_info: tuple[type[BaseException], BaseException, TracebackType]
313 | tuple[None, None, None]
314 | None = None,
315 ) -> t.Callable[[bytes], object]:
316 nonlocal status_set, headers_set
317 if exc_info:
318 try:
319 if headers_sent:
320 raise exc_info[1].with_traceback(exc_info[2]) # type: ignore[union-attr]
321 finally:
322 exc_info = None
323 elif headers_set:
324 raise AssertionError("Headers already set")
325 status_set = status
326 headers_set = headers
327 return write
329 def execute(app: wt.WSGIApplication) -> None:
330 application_iter = app(environ, start_response)
331 try:
332 for data in application_iter:
333 write(data)
334 if not headers_sent:
335 write(b"")
336 if chunk_response:
337 self.wfile.write(b"0\r\n\r\n")
338 finally:
339 if hasattr(application_iter, "close"):
340 close_iter: contextlib.AbstractContextManager[t.Any] = (
341 contextlib.closing(application_iter)
342 )
343 else:
344 close_iter = contextlib.nullcontext()
346 with close_iter, selectors.DefaultSelector() as selector:
347 # Check for any remaining data in the read socket, and discard it.
348 # This will read past request.max_content_length, but lets the
349 # client see a 413 response instead of a connection reset failure.
350 # If we supported keep-alive connections, this naive approach would
351 # break by reading the next request line. Since we know that write
352 # (above) closes every connection we can read everything.
353 selector.register(self.connection, selectors.EVENT_READ)
354 total_size = 0
355 total_reads = 0
357 # A timeout of 0 tends to fail because a client needs a small amount
358 # of time to continue sending its data.
359 while selector.select(timeout=0.01):
360 # Only read 10MB into memory at a time.
361 data = self.rfile.read(10_000_000)
362 total_size += len(data)
363 total_reads += 1
365 # Stop reading on no data, >=10GB, or 1000 reads. If a client
366 # sends more than that, they'll get a connection reset failure.
367 if (
368 not data
369 or total_size >= 10_000_000_000
370 or total_reads > 1_000
371 ):
372 break
374 try:
375 execute(self.server.app)
376 except connection_dropped_errors as e:
377 self.connection_dropped(e, environ)
378 except Exception as e:
379 if self.server.passthrough_errors:
380 raise
382 if status_sent is not None and chunk_response:
383 self.close_connection = True
385 try:
386 # if we haven't yet sent the headers but they are set
387 # we roll back to be able to set them again.
388 if status_sent is None:
389 status_set = None
390 headers_set = None
391 execute(InternalServerError())
392 except Exception:
393 pass
395 from .debug.tbtools import DebugTraceback
397 msg = DebugTraceback(e).render_traceback_text()
398 self.server.log("error", f"Error on request:\n{msg}")
400 def handle(self) -> None:
401 """Handles a request ignoring dropped connections."""
402 try:
403 super().handle()
404 except (ConnectionError, TimeoutError) as e:
405 self.connection_dropped(e)
406 except Exception as e:
407 if self.server.ssl_context is not None and is_ssl_error(e):
408 self.log_error("SSL error occurred: %s", e)
409 else:
410 raise
412 def connection_dropped(
413 self, error: BaseException, environ: wt.WSGIEnvironment | None = None
414 ) -> None:
415 """Called if the connection was closed by the client. By default
416 nothing happens.
417 """
419 def __getattr__(self, name: str) -> t.Any:
420 # All HTTP methods are handled by run_wsgi.
421 if name.startswith("do_"):
422 return self.run_wsgi
424 # All other attributes are forwarded to the base class.
425 return getattr(super(), name)
427 def address_string(self) -> str:
428 if getattr(self, "environ", None):
429 return self.environ["REMOTE_ADDR"] # type: ignore[no-any-return]
431 if not self.client_address:
432 return "<local>"
434 return self.client_address[0]
436 def port_integer(self) -> int:
437 return self.client_address[1]
439 # Escape control characters. This is defined (but private) in Python 3.12.
440 _control_char_table = str.maketrans(
441 {c: rf"\x{c:02x}" for c in [*range(0x20), *range(0x7F, 0xA0)]}
442 )
443 _control_char_table[ord("\\")] = r"\\"
445 def log_request(self, code: int | str = "-", size: int | str = "-") -> None:
446 try:
447 path = uri_to_iri(self.path)
448 msg = f"{self.command} {path} {self.request_version}"
449 except AttributeError:
450 # path isn't set if the requestline was bad
451 msg = self.requestline
453 # Escape control characters that may be in the decoded path.
454 msg = msg.translate(self._control_char_table)
455 code = str(code)
457 if code.startswith("1"): # 1xx - Informational
458 msg = _ansi_style(msg, "bold")
459 elif code == "200": # 2xx - Success
460 pass
461 elif code == "304": # 304 - Resource Not Modified
462 msg = _ansi_style(msg, "cyan")
463 elif code.startswith("3"): # 3xx - Redirection
464 msg = _ansi_style(msg, "green")
465 elif code == "404": # 404 - Resource Not Found
466 msg = _ansi_style(msg, "yellow")
467 elif code.startswith("4"): # 4xx - Client Error
468 msg = _ansi_style(msg, "bold", "red")
469 else: # 5xx, or any other response
470 msg = _ansi_style(msg, "bold", "magenta")
472 self.log("info", '"%s" %s %s', msg, code, size)
474 def log_error(self, format: str, *args: t.Any) -> None:
475 self.log("error", format, *args)
477 def log_message(self, format: str, *args: t.Any) -> None:
478 self.log("info", format, *args)
480 def log(self, type: str, message: str, *args: t.Any) -> None:
481 # an IPv6 scoped address contains "%" which breaks logging
482 address_string = self.address_string().replace("%", "%%")
483 _log(
484 type,
485 f"{address_string} - - [{self.log_date_time_string()}] {message}\n",
486 *args,
487 )
490def _ansi_style(value: str, *styles: str) -> str:
491 codes = {
492 "bold": 1,
493 "red": 31,
494 "green": 32,
495 "yellow": 33,
496 "magenta": 35,
497 "cyan": 36,
498 }
500 for style in styles:
501 value = f"\x1b[{codes[style]}m{value}"
503 return f"{value}\x1b[0m"
506def generate_adhoc_ssl_pair(
507 cn: str | None = None,
508) -> tuple[Certificate, RSAPrivateKeyWithSerialization]:
509 try:
510 from cryptography import x509
511 from cryptography.hazmat.backends import default_backend
512 from cryptography.hazmat.primitives import hashes
513 from cryptography.hazmat.primitives.asymmetric import rsa
514 from cryptography.x509.oid import NameOID
515 except ImportError:
516 raise TypeError(
517 "Using ad-hoc certificates requires the cryptography library."
518 ) from None
520 backend = default_backend()
521 pkey = rsa.generate_private_key(
522 public_exponent=65537, key_size=2048, backend=backend
523 )
525 # pretty damn sure that this is not actually accepted by anyone
526 if cn is None:
527 cn = "*"
529 subject = x509.Name(
530 [
531 x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Dummy Certificate"),
532 x509.NameAttribute(NameOID.COMMON_NAME, cn),
533 ]
534 )
536 backend = default_backend()
537 cert = (
538 x509.CertificateBuilder()
539 .subject_name(subject)
540 .issuer_name(subject)
541 .public_key(pkey.public_key())
542 .serial_number(x509.random_serial_number())
543 .not_valid_before(dt.datetime.now(dt.UTC))
544 .not_valid_after(dt.datetime.now(dt.UTC) + dt.timedelta(days=365))
545 .add_extension(x509.ExtendedKeyUsage([x509.OID_SERVER_AUTH]), critical=False)
546 .add_extension(
547 x509.SubjectAlternativeName([x509.DNSName(cn), x509.DNSName(f"*.{cn}")]),
548 critical=False,
549 )
550 .sign(pkey, hashes.SHA256(), backend)
551 )
552 return cert, pkey
555def make_ssl_devcert(
556 base_path: str, host: str | None = None, cn: str | None = None
557) -> tuple[str, str]:
558 """Creates an SSL key for development. This should be used instead of
559 the ``'adhoc'`` key which generates a new cert on each server start.
560 It accepts a path for where it should store the key and cert and
561 either a host or CN. If a host is given it will use the CN
562 ``*.host/CN=host``.
564 For more information see :func:`run_simple`.
566 .. versionadded:: 0.9
568 :param base_path: the path to the certificate and key. The extension
569 ``.crt`` is added for the certificate, ``.key`` is
570 added for the key.
571 :param host: the name of the host. This can be used as an alternative
572 for the `cn`.
573 :param cn: the `CN` to use.
574 """
576 if host is not None:
577 cn = host
578 cert, pkey = generate_adhoc_ssl_pair(cn=cn)
580 from cryptography.hazmat.primitives import serialization
582 cert_file = f"{base_path}.crt"
583 pkey_file = f"{base_path}.key"
585 with open(cert_file, "wb") as f:
586 f.write(cert.public_bytes(serialization.Encoding.PEM))
587 with open(pkey_file, "wb") as f:
588 f.write(
589 pkey.private_bytes(
590 encoding=serialization.Encoding.PEM,
591 format=serialization.PrivateFormat.TraditionalOpenSSL,
592 encryption_algorithm=serialization.NoEncryption(),
593 )
594 )
596 return cert_file, pkey_file
599def generate_adhoc_ssl_context() -> ssl.SSLContext:
600 """Generates an adhoc SSL context for the development server."""
601 import atexit
602 import tempfile
604 cert, pkey = generate_adhoc_ssl_pair()
606 from cryptography.hazmat.primitives import serialization
608 with tempfile.NamedTemporaryFile("wb", delete=False) as cert_file:
609 cert_file.write(cert.public_bytes(serialization.Encoding.PEM))
611 with tempfile.NamedTemporaryFile("wb", delete=False) as pkey_file:
612 pkey_file.write(
613 pkey.private_bytes(
614 encoding=serialization.Encoding.PEM,
615 format=serialization.PrivateFormat.TraditionalOpenSSL,
616 encryption_algorithm=serialization.NoEncryption(),
617 )
618 )
620 atexit.register(os.remove, cert_file.name)
621 atexit.register(os.remove, pkey_file.name)
622 ctx = load_ssl_context(cert_file.name, pkey_file.name)
623 return ctx
626def load_ssl_context(
627 cert_file: str, pkey_file: str | None = None, protocol: int | None = None
628) -> ssl.SSLContext:
629 """Loads SSL context from cert/private key files and optional protocol.
630 Many parameters are directly taken from the API of
631 :py:class:`ssl.SSLContext`.
633 :param cert_file: Path of the certificate to use.
634 :param pkey_file: Path of the private key to use. If not given, the key
635 will be obtained from the certificate file.
636 :param protocol: A ``PROTOCOL`` constant from the :mod:`ssl` module.
637 Defaults to :data:`ssl.PROTOCOL_TLS_SERVER`.
638 """
639 if protocol is None:
640 protocol = ssl.PROTOCOL_TLS_SERVER
642 ctx = ssl.SSLContext(protocol)
643 ctx.load_cert_chain(cert_file, pkey_file)
644 return ctx
647def is_ssl_error(error: Exception | None = None) -> bool:
648 """Checks if the given error (or the current one) is an SSL error."""
649 if error is None:
650 error = t.cast(Exception, sys.exc_info()[1])
651 return isinstance(error, ssl.SSLError)
654def select_address_family(host: str, port: int) -> socket.AddressFamily:
655 """Return ``AF_INET4``, ``AF_INET6``, or ``AF_UNIX`` depending on
656 the host and port."""
657 if host.startswith("unix://"):
658 return socket.AF_UNIX
659 elif ":" in host and hasattr(socket, "AF_INET6"):
660 return socket.AF_INET6
661 return socket.AF_INET
664def get_sockaddr(
665 host: str, port: int, family: socket.AddressFamily
666) -> tuple[str, int] | str:
667 """Return a fully qualified socket address that can be passed to
668 :func:`socket.bind`."""
669 if family == af_unix:
670 # Absolute path avoids IDNA encoding error when path starts with dot.
671 return os.path.abspath(host.partition("://")[2])
672 try:
673 res = socket.getaddrinfo(
674 host, port, family, socket.SOCK_STREAM, socket.IPPROTO_TCP
675 )
676 except socket.gaierror:
677 return host, port
678 return res[0][4] # type: ignore[return-value]
681def get_interface_ip(family: socket.AddressFamily) -> str:
682 """Get the IP address of an external interface. Used when binding to
683 0.0.0.0 or ::1 to show a more useful URL.
685 :meta private:
686 """
687 # arbitrary private address
688 host = "fd31:f903:5ab5:1::1" if family == socket.AF_INET6 else "10.253.155.219"
690 with socket.socket(family, socket.SOCK_DGRAM) as s:
691 try:
692 s.connect((host, 58162))
693 except OSError:
694 return "::1" if family == socket.AF_INET6 else "127.0.0.1"
696 return s.getsockname()[0] # type: ignore[no-any-return]
699class BaseWSGIServer(HTTPServer):
700 """A WSGI server that that handles one request at a time.
702 Use :func:`make_server` to create a server instance.
703 """
705 multithread = False
706 multiprocess = False
707 request_queue_size = LISTEN_QUEUE
708 allow_reuse_address = True
710 def __init__(
711 self,
712 host: str,
713 port: int,
714 app: wt.WSGIApplication,
715 handler: type[WSGIRequestHandler] | None = None,
716 passthrough_errors: bool = False,
717 ssl_context: _TSSLContextArg = None,
718 fd: int | None = None,
719 ) -> None:
720 if handler is None:
721 handler = WSGIRequestHandler
723 # If the handler doesn't directly set a protocol version and
724 # thread or process workers are used, then allow chunked
725 # responses and keep-alive connections by enabling HTTP/1.1.
726 if "protocol_version" not in vars(handler) and (
727 self.multithread or self.multiprocess
728 ):
729 handler.protocol_version = "HTTP/1.1"
731 self.host = host
732 self.port = port
733 self.app = app
734 self.passthrough_errors = passthrough_errors
736 self.address_family = address_family = select_address_family(host, port)
737 server_address = get_sockaddr(host, int(port), address_family)
739 # Remove a leftover Unix socket file from a previous run. Don't
740 # remove a file that was set up by run_simple.
741 if address_family == af_unix and fd is None:
742 server_address = t.cast(str, server_address)
744 if os.path.exists(server_address):
745 os.unlink(server_address)
747 # Bind and activate will be handled manually, it should only
748 # happen if we're not using a socket that was already set up.
749 super().__init__(
750 server_address, # type: ignore[arg-type]
751 handler,
752 bind_and_activate=False,
753 )
755 if fd is None:
756 # No existing socket descriptor, do bind_and_activate=True.
757 try:
758 self.server_bind()
759 self.server_activate()
760 except OSError as e:
761 # Catch connection issues and show them without the traceback. Show
762 # extra instructions for address not found, and for macOS.
763 self.server_close()
764 print(e.strerror, file=sys.stderr)
766 if e.errno == errno.EADDRINUSE:
767 print(
768 f"Port {port} is in use by another program. Either identify and"
769 " stop that program, or start the server with a different"
770 " port.",
771 file=sys.stderr,
772 )
774 if sys.platform == "darwin" and port == 5000:
775 print(
776 "On macOS, try searching for and disabling"
777 " 'AirPlay Receiver' in System Settings.",
778 file=sys.stderr,
779 )
781 sys.exit(1)
782 except BaseException:
783 self.server_close()
784 raise
785 else:
786 # TCPServer automatically opens a socket even if bind_and_activate is False.
787 # Close it to silence a ResourceWarning.
788 self.server_close()
790 # Use the passed in socket directly.
791 self.socket = socket.fromfd(fd, address_family, socket.SOCK_STREAM)
792 self.server_address = self.socket.getsockname()
794 if address_family != af_unix:
795 # If port was 0, this will record the bound port.
796 self.port = self.server_address[1]
798 if ssl_context is not None:
799 if isinstance(ssl_context, tuple):
800 ssl_context = load_ssl_context(*ssl_context)
801 elif ssl_context == "adhoc":
802 ssl_context = generate_adhoc_ssl_context()
804 self.socket = ssl_context.wrap_socket(self.socket, server_side=True)
805 self.ssl_context: ssl.SSLContext | None = ssl_context
806 else:
807 self.ssl_context = None
809 import importlib.metadata
811 self._server_version = f"Werkzeug/{importlib.metadata.version('werkzeug')}"
813 def log(self, type: str, message: str, *args: t.Any) -> None:
814 _log(type, message, *args)
816 def serve_forever(self, poll_interval: float = 0.5) -> None:
817 try:
818 super().serve_forever(poll_interval=poll_interval)
819 except KeyboardInterrupt:
820 pass
821 finally:
822 self.server_close()
824 def handle_error(
825 self, request: t.Any, client_address: tuple[str, int] | str
826 ) -> None:
827 if self.passthrough_errors:
828 raise
830 return super().handle_error(request, client_address)
832 def log_startup(self) -> None:
833 """Show information about the address when starting the server."""
834 dev_warning = (
835 "WARNING: This is a development server. Do not use it in a production"
836 " deployment. Use a production WSGI server instead."
837 )
838 dev_warning = _ansi_style(dev_warning, "bold", "red")
839 messages = [dev_warning]
841 if self.address_family == af_unix:
842 messages.append(f" * Running on {self.host}")
843 else:
844 scheme = "http" if self.ssl_context is None else "https"
845 display_hostname = self.host
847 if self.host in {"0.0.0.0", "::"}:
848 messages.append(f" * Running on all addresses ({self.host})")
850 if self.host == "0.0.0.0":
851 localhost = "127.0.0.1"
852 display_hostname = get_interface_ip(socket.AF_INET)
853 else:
854 localhost = "[::1]"
855 display_hostname = get_interface_ip(socket.AF_INET6)
857 messages.append(f" * Running on {scheme}://{localhost}:{self.port}")
859 if ":" in display_hostname:
860 display_hostname = f"[{display_hostname}]"
862 messages.append(f" * Running on {scheme}://{display_hostname}:{self.port}")
864 _log("info", "\n".join(messages))
867class ThreadedWSGIServer(socketserver.ThreadingMixIn, BaseWSGIServer):
868 """A WSGI server that handles concurrent requests in separate
869 threads.
871 Use :func:`make_server` to create a server instance.
872 """
874 multithread = True
875 daemon_threads = True
878class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer):
879 """A WSGI server that handles concurrent requests in separate forked
880 processes.
882 Use :func:`make_server` to create a server instance.
883 """
885 multiprocess = True
887 def __init__(
888 self,
889 host: str,
890 port: int,
891 app: wt.WSGIApplication,
892 processes: int = 40,
893 handler: type[WSGIRequestHandler] | None = None,
894 passthrough_errors: bool = False,
895 ssl_context: _TSSLContextArg = None,
896 fd: int | None = None,
897 ) -> None:
898 if not can_fork:
899 raise ValueError("Your platform does not support forking.")
901 super().__init__(host, port, app, handler, passthrough_errors, ssl_context, fd)
902 self.max_children = processes
905def make_server(
906 host: str,
907 port: int,
908 app: wt.WSGIApplication,
909 threaded: bool = False,
910 processes: int = 1,
911 request_handler: type[WSGIRequestHandler] | None = None,
912 passthrough_errors: bool = False,
913 ssl_context: _TSSLContextArg = None,
914 fd: int | None = None,
915) -> BaseWSGIServer:
916 """Create an appropriate WSGI server instance based on the value of
917 ``threaded`` and ``processes``.
919 This is called from :func:`run_simple`, but can be used separately
920 to have access to the server object, such as to run it in a separate
921 thread.
923 See :func:`run_simple` for parameter docs.
924 """
925 if threaded and processes > 1:
926 raise ValueError("Cannot have a multi-thread and multi-process server.")
928 if threaded:
929 return ThreadedWSGIServer(
930 host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd
931 )
933 if processes > 1:
934 return ForkingWSGIServer(
935 host,
936 port,
937 app,
938 processes,
939 request_handler,
940 passthrough_errors,
941 ssl_context,
942 fd=fd,
943 )
945 return BaseWSGIServer(
946 host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd
947 )
950def is_running_from_reloader() -> bool:
951 """Check if the server is running as a subprocess within the
952 Werkzeug reloader.
954 .. versionadded:: 0.10
955 """
956 return os.environ.get("WERKZEUG_RUN_MAIN") == "true"
959def run_simple(
960 hostname: str,
961 port: int,
962 application: wt.WSGIApplication,
963 use_reloader: bool = False,
964 use_debugger: bool = False,
965 use_evalex: bool = True,
966 extra_files: t.Iterable[str] | None = None,
967 exclude_patterns: t.Iterable[str] | None = None,
968 reloader_interval: int = 1,
969 reloader_type: str = "auto",
970 threaded: bool = False,
971 processes: int = 1,
972 request_handler: type[WSGIRequestHandler] | None = None,
973 static_files: dict[str, str | tuple[str, str]] | None = None,
974 passthrough_errors: bool = False,
975 ssl_context: _TSSLContextArg = None,
976) -> None:
977 """Start a development server for a WSGI application. Various
978 optional features can be enabled.
980 .. warning::
982 Do not use the development server when deploying to production.
983 It is intended for use only during local development. It is not
984 designed to be particularly efficient, stable, or secure.
986 :param hostname: The host to bind to, for example ``'localhost'``.
987 Can be a domain, IPv4 or IPv6 address, or file path starting
988 with ``unix://`` for a Unix socket.
989 :param port: The port to bind to, for example ``8080``. Using ``0``
990 tells the OS to pick a random free port.
991 :param application: The WSGI application to run.
992 :param use_reloader: Use a reloader process to restart the server
993 process when files are changed.
994 :param use_debugger: Use Werkzeug's debugger, which will show
995 formatted tracebacks on unhandled exceptions.
996 :param use_evalex: Make the debugger interactive. A Python terminal
997 can be opened for any frame in the traceback. Some protection is
998 provided by requiring a PIN, but this should never be enabled
999 on a publicly visible server.
1000 :param extra_files: The reloader will watch these files for changes
1001 in addition to Python modules. For example, watch a
1002 configuration file.
1003 :param exclude_patterns: The reloader will ignore changes to any
1004 files matching these :mod:`fnmatch` patterns. For example,
1005 ignore cache files.
1006 :param reloader_interval: How often the reloader tries to check for
1007 changes.
1008 :param reloader_type: The reloader to use. The ``'stat'`` reloader
1009 is built in, but may require significant CPU to watch files. The
1010 ``'watchdog'`` reloader is much more efficient but requires
1011 installing the ``watchdog`` package first.
1012 :param threaded: Handle concurrent requests using threads. Cannot be
1013 used with ``processes``.
1014 :param processes: Handle concurrent requests using up to this number
1015 of processes. Cannot be used with ``threaded``.
1016 :param request_handler: Use a different
1017 :class:`~BaseHTTPServer.BaseHTTPRequestHandler` subclass to
1018 handle requests.
1019 :param static_files: A dict mapping URL prefixes to directories to
1020 serve static files from using
1021 :class:`~werkzeug.middleware.SharedDataMiddleware`.
1022 :param passthrough_errors: Don't catch unhandled exceptions at the
1023 server level, let the server crash instead. If ``use_debugger``
1024 is enabled, the debugger will still catch such errors.
1025 :param ssl_context: Configure TLS to serve over HTTPS. Can be an
1026 :class:`ssl.SSLContext` object, a ``(cert_file, key_file)``
1027 tuple to create a typical context, or the string ``'adhoc'`` to
1028 generate a temporary self-signed certificate.
1030 .. versionchanged:: 2.1
1031 Instructions are shown for dealing with an "address already in
1032 use" error.
1034 .. versionchanged:: 2.1
1035 Running on ``0.0.0.0`` or ``::`` shows the loopback IP in
1036 addition to a real IP.
1038 .. versionchanged:: 2.1
1039 The command-line interface was removed.
1041 .. versionchanged:: 2.0
1042 Running on ``0.0.0.0`` or ``::`` shows a real IP address that
1043 was bound as well as a warning not to run the development server
1044 in production.
1046 .. versionchanged:: 2.0
1047 The ``exclude_patterns`` parameter was added.
1049 .. versionchanged:: 0.15
1050 Bind to a Unix socket by passing a ``hostname`` that starts with
1051 ``unix://``.
1053 .. versionchanged:: 0.10
1054 Improved the reloader and added support for changing the backend
1055 through the ``reloader_type`` parameter.
1057 .. versionchanged:: 0.9
1058 A command-line interface was added.
1060 .. versionchanged:: 0.8
1061 ``ssl_context`` can be a tuple of paths to the certificate and
1062 private key files.
1064 .. versionchanged:: 0.6
1065 The ``ssl_context`` parameter was added.
1067 .. versionchanged:: 0.5
1068 The ``static_files`` and ``passthrough_errors`` parameters were
1069 added.
1070 """
1071 if not isinstance(port, int):
1072 raise TypeError("port must be an integer")
1074 if static_files:
1075 from .middleware.shared_data import SharedDataMiddleware
1077 application = SharedDataMiddleware(application, static_files)
1079 if use_debugger:
1080 from .debug import DebuggedApplication
1082 application = DebuggedApplication(application, evalex=use_evalex)
1084 # Allow the specified hostname to use the debugger, in addition to
1085 # localhost domains.
1086 if hostname not in {"0.0.0.0", "[::]"}:
1087 application.trusted_hosts.append(hostname)
1089 if not is_running_from_reloader():
1090 fd = None
1091 else:
1092 fd = int(os.environ["WERKZEUG_SERVER_FD"])
1094 srv = make_server(
1095 hostname,
1096 port,
1097 application,
1098 threaded,
1099 processes,
1100 request_handler,
1101 passthrough_errors,
1102 ssl_context,
1103 fd=fd,
1104 )
1105 srv.socket.set_inheritable(True)
1106 os.environ["WERKZEUG_SERVER_FD"] = str(srv.fileno())
1108 if not is_running_from_reloader():
1109 srv.log_startup()
1110 _log("info", _ansi_style("Press CTRL+C to quit", "yellow"))
1112 if use_reloader:
1113 from ._reloader import run_with_reloader
1115 try:
1116 run_with_reloader(
1117 srv.serve_forever,
1118 extra_files=extra_files,
1119 exclude_patterns=exclude_patterns,
1120 interval=reloader_interval,
1121 reloader_type=reloader_type,
1122 )
1123 finally:
1124 srv.server_close()
1125 else:
1126 srv.serve_forever()