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