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