|
from __future__ import annotations |
|
|
|
import asyncio |
|
import logging |
|
import os |
|
import platform |
|
import ssl |
|
import sys |
|
import warnings |
|
from configparser import RawConfigParser |
|
from typing import IO, Any, Callable |
|
|
|
import click |
|
|
|
import uvicorn |
|
from uvicorn._types import ASGIApplication |
|
from uvicorn.config import ( |
|
HTTP_PROTOCOLS, |
|
INTERFACES, |
|
LIFESPAN, |
|
LOG_LEVELS, |
|
LOGGING_CONFIG, |
|
LOOP_SETUPS, |
|
SSL_PROTOCOL_VERSION, |
|
WS_PROTOCOLS, |
|
Config, |
|
HTTPProtocolType, |
|
InterfaceType, |
|
LifespanType, |
|
LoopSetupType, |
|
WSProtocolType, |
|
) |
|
from uvicorn.server import Server |
|
from uvicorn.supervisors import ChangeReload, Multiprocess |
|
|
|
LEVEL_CHOICES = click.Choice(list(LOG_LEVELS.keys())) |
|
HTTP_CHOICES = click.Choice(list(HTTP_PROTOCOLS.keys())) |
|
WS_CHOICES = click.Choice(list(WS_PROTOCOLS.keys())) |
|
LIFESPAN_CHOICES = click.Choice(list(LIFESPAN.keys())) |
|
LOOP_CHOICES = click.Choice([key for key in LOOP_SETUPS.keys() if key != "none"]) |
|
INTERFACE_CHOICES = click.Choice(INTERFACES) |
|
|
|
STARTUP_FAILURE = 3 |
|
|
|
logger = logging.getLogger("uvicorn.error") |
|
|
|
|
|
def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> None: |
|
if not value or ctx.resilient_parsing: |
|
return |
|
click.echo( |
|
"Running uvicorn {version} with {py_implementation} {py_version} on {system}".format( |
|
version=uvicorn.__version__, |
|
py_implementation=platform.python_implementation(), |
|
py_version=platform.python_version(), |
|
system=platform.system(), |
|
) |
|
) |
|
ctx.exit() |
|
|
|
|
|
@click.command(context_settings={"auto_envvar_prefix": "UVICORN"}) |
|
@click.argument("app", envvar="UVICORN_APP") |
|
@click.option( |
|
"--host", |
|
type=str, |
|
default="127.0.0.1", |
|
help="Bind socket to this host.", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--port", |
|
type=int, |
|
default=8000, |
|
help="Bind socket to this port. If 0, an available port will be picked.", |
|
show_default=True, |
|
) |
|
@click.option("--uds", type=str, default=None, help="Bind to a UNIX domain socket.") |
|
@click.option("--fd", type=int, default=None, help="Bind to socket from this file descriptor.") |
|
@click.option("--reload", is_flag=True, default=False, help="Enable auto-reload.") |
|
@click.option( |
|
"--reload-dir", |
|
"reload_dirs", |
|
multiple=True, |
|
help="Set reload directories explicitly, instead of using the current working directory.", |
|
type=click.Path(exists=True), |
|
) |
|
@click.option( |
|
"--reload-include", |
|
"reload_includes", |
|
multiple=True, |
|
help="Set glob patterns to include while watching for files. Includes '*.py' " |
|
"by default; these defaults can be overridden with `--reload-exclude`. " |
|
"This option has no effect unless watchfiles is installed.", |
|
) |
|
@click.option( |
|
"--reload-exclude", |
|
"reload_excludes", |
|
multiple=True, |
|
help="Set glob patterns to exclude while watching for files. Includes " |
|
"'.*, .py[cod], .sw.*, ~*' by default; these defaults can be overridden " |
|
"with `--reload-include`. This option has no effect unless watchfiles is " |
|
"installed.", |
|
) |
|
@click.option( |
|
"--reload-delay", |
|
type=float, |
|
default=0.25, |
|
show_default=True, |
|
help="Delay between previous and next check if application needs to be. Defaults to 0.25s.", |
|
) |
|
@click.option( |
|
"--workers", |
|
default=None, |
|
type=int, |
|
help="Number of worker processes. Defaults to the $WEB_CONCURRENCY environment" |
|
" variable if available, or 1. Not valid with --reload.", |
|
) |
|
@click.option( |
|
"--loop", |
|
type=LOOP_CHOICES, |
|
default="auto", |
|
help="Event loop implementation.", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--http", |
|
type=HTTP_CHOICES, |
|
default="auto", |
|
help="HTTP protocol implementation.", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--ws", |
|
type=WS_CHOICES, |
|
default="auto", |
|
help="WebSocket protocol implementation.", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--ws-max-size", |
|
type=int, |
|
default=16777216, |
|
help="WebSocket max size message in bytes", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--ws-max-queue", |
|
type=int, |
|
default=32, |
|
help="The maximum length of the WebSocket message queue.", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--ws-ping-interval", |
|
type=float, |
|
default=20.0, |
|
help="WebSocket ping interval in seconds.", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--ws-ping-timeout", |
|
type=float, |
|
default=20.0, |
|
help="WebSocket ping timeout in seconds.", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--ws-per-message-deflate", |
|
type=bool, |
|
default=True, |
|
help="WebSocket per-message-deflate compression", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--lifespan", |
|
type=LIFESPAN_CHOICES, |
|
default="auto", |
|
help="Lifespan implementation.", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--interface", |
|
type=INTERFACE_CHOICES, |
|
default="auto", |
|
help="Select ASGI3, ASGI2, or WSGI as the application interface.", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--env-file", |
|
type=click.Path(exists=True), |
|
default=None, |
|
help="Environment configuration file.", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--log-config", |
|
type=click.Path(exists=True), |
|
default=None, |
|
help="Logging configuration file. Supported formats: .ini, .json, .yaml.", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--log-level", |
|
type=LEVEL_CHOICES, |
|
default=None, |
|
help="Log level. [default: info]", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--access-log/--no-access-log", |
|
is_flag=True, |
|
default=True, |
|
help="Enable/Disable access log.", |
|
) |
|
@click.option( |
|
"--use-colors/--no-use-colors", |
|
is_flag=True, |
|
default=None, |
|
help="Enable/Disable colorized logging.", |
|
) |
|
@click.option( |
|
"--proxy-headers/--no-proxy-headers", |
|
is_flag=True, |
|
default=True, |
|
help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For, X-Forwarded-Port to populate remote address info.", |
|
) |
|
@click.option( |
|
"--server-header/--no-server-header", |
|
is_flag=True, |
|
default=True, |
|
help="Enable/Disable default Server header.", |
|
) |
|
@click.option( |
|
"--date-header/--no-date-header", |
|
is_flag=True, |
|
default=True, |
|
help="Enable/Disable default Date header.", |
|
) |
|
@click.option( |
|
"--forwarded-allow-ips", |
|
type=str, |
|
default=None, |
|
help="Comma separated list of IP Addresses, IP Networks, or literals " |
|
"(e.g. UNIX Socket path) to trust with proxy headers. Defaults to the " |
|
"$FORWARDED_ALLOW_IPS environment variable if available, or '127.0.0.1'. " |
|
"The literal '*' means trust everything.", |
|
) |
|
@click.option( |
|
"--root-path", |
|
type=str, |
|
default="", |
|
help="Set the ASGI 'root_path' for applications submounted below a given URL path.", |
|
) |
|
@click.option( |
|
"--limit-concurrency", |
|
type=int, |
|
default=None, |
|
help="Maximum number of concurrent connections or tasks to allow, before issuing HTTP 503 responses.", |
|
) |
|
@click.option( |
|
"--backlog", |
|
type=int, |
|
default=2048, |
|
help="Maximum number of connections to hold in backlog", |
|
) |
|
@click.option( |
|
"--limit-max-requests", |
|
type=int, |
|
default=None, |
|
help="Maximum number of requests to service before terminating the process.", |
|
) |
|
@click.option( |
|
"--timeout-keep-alive", |
|
type=int, |
|
default=5, |
|
help="Close Keep-Alive connections if no new data is received within this timeout.", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--timeout-graceful-shutdown", |
|
type=int, |
|
default=None, |
|
help="Maximum number of seconds to wait for graceful shutdown.", |
|
) |
|
@click.option("--ssl-keyfile", type=str, default=None, help="SSL key file", show_default=True) |
|
@click.option( |
|
"--ssl-certfile", |
|
type=str, |
|
default=None, |
|
help="SSL certificate file", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--ssl-keyfile-password", |
|
type=str, |
|
default=None, |
|
help="SSL keyfile password", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--ssl-version", |
|
type=int, |
|
default=int(SSL_PROTOCOL_VERSION), |
|
help="SSL version to use (see stdlib ssl module's)", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--ssl-cert-reqs", |
|
type=int, |
|
default=int(ssl.CERT_NONE), |
|
help="Whether client certificate is required (see stdlib ssl module's)", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--ssl-ca-certs", |
|
type=str, |
|
default=None, |
|
help="CA certificates file", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--ssl-ciphers", |
|
type=str, |
|
default="TLSv1", |
|
help="Ciphers to use (see stdlib ssl module's)", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--header", |
|
"headers", |
|
multiple=True, |
|
help="Specify custom default HTTP response headers as a Name:Value pair", |
|
) |
|
@click.option( |
|
"--version", |
|
is_flag=True, |
|
callback=print_version, |
|
expose_value=False, |
|
is_eager=True, |
|
help="Display the uvicorn version and exit.", |
|
) |
|
@click.option( |
|
"--app-dir", |
|
default="", |
|
show_default=True, |
|
help="Look for APP in the specified directory, by adding this to the PYTHONPATH." |
|
" Defaults to the current working directory.", |
|
) |
|
@click.option( |
|
"--h11-max-incomplete-event-size", |
|
"h11_max_incomplete_event_size", |
|
type=int, |
|
default=None, |
|
help="For h11, the maximum number of bytes to buffer of an incomplete event.", |
|
) |
|
@click.option( |
|
"--factory", |
|
is_flag=True, |
|
default=False, |
|
help="Treat APP as an application factory, i.e. a () -> <ASGI app> callable.", |
|
show_default=True, |
|
) |
|
def main( |
|
app: str, |
|
host: str, |
|
port: int, |
|
uds: str, |
|
fd: int, |
|
loop: LoopSetupType, |
|
http: HTTPProtocolType, |
|
ws: WSProtocolType, |
|
ws_max_size: int, |
|
ws_max_queue: int, |
|
ws_ping_interval: float, |
|
ws_ping_timeout: float, |
|
ws_per_message_deflate: bool, |
|
lifespan: LifespanType, |
|
interface: InterfaceType, |
|
reload: bool, |
|
reload_dirs: list[str], |
|
reload_includes: list[str], |
|
reload_excludes: list[str], |
|
reload_delay: float, |
|
workers: int, |
|
env_file: str, |
|
log_config: str, |
|
log_level: str, |
|
access_log: bool, |
|
proxy_headers: bool, |
|
server_header: bool, |
|
date_header: bool, |
|
forwarded_allow_ips: str, |
|
root_path: str, |
|
limit_concurrency: int, |
|
backlog: int, |
|
limit_max_requests: int, |
|
timeout_keep_alive: int, |
|
timeout_graceful_shutdown: int | None, |
|
ssl_keyfile: str, |
|
ssl_certfile: str, |
|
ssl_keyfile_password: str, |
|
ssl_version: int, |
|
ssl_cert_reqs: int, |
|
ssl_ca_certs: str, |
|
ssl_ciphers: str, |
|
headers: list[str], |
|
use_colors: bool, |
|
app_dir: str, |
|
h11_max_incomplete_event_size: int | None, |
|
factory: bool, |
|
) -> None: |
|
run( |
|
app, |
|
host=host, |
|
port=port, |
|
uds=uds, |
|
fd=fd, |
|
loop=loop, |
|
http=http, |
|
ws=ws, |
|
ws_max_size=ws_max_size, |
|
ws_max_queue=ws_max_queue, |
|
ws_ping_interval=ws_ping_interval, |
|
ws_ping_timeout=ws_ping_timeout, |
|
ws_per_message_deflate=ws_per_message_deflate, |
|
lifespan=lifespan, |
|
env_file=env_file, |
|
log_config=LOGGING_CONFIG if log_config is None else log_config, |
|
log_level=log_level, |
|
access_log=access_log, |
|
interface=interface, |
|
reload=reload, |
|
reload_dirs=reload_dirs or None, |
|
reload_includes=reload_includes or None, |
|
reload_excludes=reload_excludes or None, |
|
reload_delay=reload_delay, |
|
workers=workers, |
|
proxy_headers=proxy_headers, |
|
server_header=server_header, |
|
date_header=date_header, |
|
forwarded_allow_ips=forwarded_allow_ips, |
|
root_path=root_path, |
|
limit_concurrency=limit_concurrency, |
|
backlog=backlog, |
|
limit_max_requests=limit_max_requests, |
|
timeout_keep_alive=timeout_keep_alive, |
|
timeout_graceful_shutdown=timeout_graceful_shutdown, |
|
ssl_keyfile=ssl_keyfile, |
|
ssl_certfile=ssl_certfile, |
|
ssl_keyfile_password=ssl_keyfile_password, |
|
ssl_version=ssl_version, |
|
ssl_cert_reqs=ssl_cert_reqs, |
|
ssl_ca_certs=ssl_ca_certs, |
|
ssl_ciphers=ssl_ciphers, |
|
headers=[header.split(":", 1) for header in headers], |
|
use_colors=use_colors, |
|
factory=factory, |
|
app_dir=app_dir, |
|
h11_max_incomplete_event_size=h11_max_incomplete_event_size, |
|
) |
|
|
|
|
|
def run( |
|
app: ASGIApplication | Callable[..., Any] | str, |
|
*, |
|
host: str = "127.0.0.1", |
|
port: int = 8000, |
|
uds: str | None = None, |
|
fd: int | None = None, |
|
loop: LoopSetupType = "auto", |
|
http: type[asyncio.Protocol] | HTTPProtocolType = "auto", |
|
ws: type[asyncio.Protocol] | WSProtocolType = "auto", |
|
ws_max_size: int = 16777216, |
|
ws_max_queue: int = 32, |
|
ws_ping_interval: float | None = 20.0, |
|
ws_ping_timeout: float | None = 20.0, |
|
ws_per_message_deflate: bool = True, |
|
lifespan: LifespanType = "auto", |
|
interface: InterfaceType = "auto", |
|
reload: bool = False, |
|
reload_dirs: list[str] | str | None = None, |
|
reload_includes: list[str] | str | None = None, |
|
reload_excludes: list[str] | str | None = None, |
|
reload_delay: float = 0.25, |
|
workers: int | None = None, |
|
env_file: str | os.PathLike[str] | None = None, |
|
log_config: dict[str, Any] | str | RawConfigParser | IO[Any] | None = LOGGING_CONFIG, |
|
log_level: str | int | None = None, |
|
access_log: bool = True, |
|
proxy_headers: bool = True, |
|
server_header: bool = True, |
|
date_header: bool = True, |
|
forwarded_allow_ips: list[str] | str | None = None, |
|
root_path: str = "", |
|
limit_concurrency: int | None = None, |
|
backlog: int = 2048, |
|
limit_max_requests: int | None = None, |
|
timeout_keep_alive: int = 5, |
|
timeout_graceful_shutdown: int | None = None, |
|
ssl_keyfile: str | os.PathLike[str] | None = None, |
|
ssl_certfile: str | os.PathLike[str] | None = None, |
|
ssl_keyfile_password: str | None = None, |
|
ssl_version: int = SSL_PROTOCOL_VERSION, |
|
ssl_cert_reqs: int = ssl.CERT_NONE, |
|
ssl_ca_certs: str | None = None, |
|
ssl_ciphers: str = "TLSv1", |
|
headers: list[tuple[str, str]] | None = None, |
|
use_colors: bool | None = None, |
|
app_dir: str | None = None, |
|
factory: bool = False, |
|
h11_max_incomplete_event_size: int | None = None, |
|
) -> None: |
|
if app_dir is not None: |
|
sys.path.insert(0, app_dir) |
|
|
|
config = Config( |
|
app, |
|
host=host, |
|
port=port, |
|
uds=uds, |
|
fd=fd, |
|
loop=loop, |
|
http=http, |
|
ws=ws, |
|
ws_max_size=ws_max_size, |
|
ws_max_queue=ws_max_queue, |
|
ws_ping_interval=ws_ping_interval, |
|
ws_ping_timeout=ws_ping_timeout, |
|
ws_per_message_deflate=ws_per_message_deflate, |
|
lifespan=lifespan, |
|
interface=interface, |
|
reload=reload, |
|
reload_dirs=reload_dirs, |
|
reload_includes=reload_includes, |
|
reload_excludes=reload_excludes, |
|
reload_delay=reload_delay, |
|
workers=workers, |
|
env_file=env_file, |
|
log_config=log_config, |
|
log_level=log_level, |
|
access_log=access_log, |
|
proxy_headers=proxy_headers, |
|
server_header=server_header, |
|
date_header=date_header, |
|
forwarded_allow_ips=forwarded_allow_ips, |
|
root_path=root_path, |
|
limit_concurrency=limit_concurrency, |
|
backlog=backlog, |
|
limit_max_requests=limit_max_requests, |
|
timeout_keep_alive=timeout_keep_alive, |
|
timeout_graceful_shutdown=timeout_graceful_shutdown, |
|
ssl_keyfile=ssl_keyfile, |
|
ssl_certfile=ssl_certfile, |
|
ssl_keyfile_password=ssl_keyfile_password, |
|
ssl_version=ssl_version, |
|
ssl_cert_reqs=ssl_cert_reqs, |
|
ssl_ca_certs=ssl_ca_certs, |
|
ssl_ciphers=ssl_ciphers, |
|
headers=headers, |
|
use_colors=use_colors, |
|
factory=factory, |
|
h11_max_incomplete_event_size=h11_max_incomplete_event_size, |
|
) |
|
server = Server(config=config) |
|
|
|
if (config.reload or config.workers > 1) and not isinstance(app, str): |
|
logger = logging.getLogger("uvicorn.error") |
|
logger.warning("You must pass the application as an import string to enable 'reload' or 'workers'.") |
|
sys.exit(1) |
|
|
|
try: |
|
if config.should_reload: |
|
sock = config.bind_socket() |
|
ChangeReload(config, target=server.run, sockets=[sock]).run() |
|
elif config.workers > 1: |
|
sock = config.bind_socket() |
|
Multiprocess(config, target=server.run, sockets=[sock]).run() |
|
else: |
|
server.run() |
|
except KeyboardInterrupt: |
|
pass |
|
finally: |
|
if config.uds and os.path.exists(config.uds): |
|
os.remove(config.uds) |
|
|
|
if not server.started and not config.should_reload and config.workers == 1: |
|
sys.exit(STARTUP_FAILURE) |
|
|
|
|
|
def __getattr__(name: str) -> Any: |
|
if name == "ServerState": |
|
warnings.warn( |
|
"uvicorn.main.ServerState is deprecated, use uvicorn.server.ServerState instead.", |
|
DeprecationWarning, |
|
) |
|
from uvicorn.server import ServerState |
|
|
|
return ServerState |
|
raise AttributeError(f"module {__name__} has no attribute {name}") |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |
|
|