123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- import argparse
- import logging
- import sys
- from argparse import ArgumentError, Namespace
-
- from asgiref.compatibility import guarantee_single_callable
-
- from .access import AccessLogGenerator
- from .endpoints import build_endpoint_description_strings
- from .server import Server
- from .utils import import_by_path
-
- logger = logging.getLogger(__name__)
-
- DEFAULT_HOST = "127.0.0.1"
- DEFAULT_PORT = 8000
-
-
- class CommandLineInterface:
- """
- Acts as the main CLI entry point for running the server.
- """
-
- description = "Django HTTP/WebSocket server"
-
- server_class = Server
-
- def __init__(self):
- self.parser = argparse.ArgumentParser(description=self.description)
- self.parser.add_argument(
- "-p", "--port", type=int, help="Port number to listen on", default=None
- )
- self.parser.add_argument(
- "-b",
- "--bind",
- dest="host",
- help="The host/address to bind to",
- default=None,
- )
- self.parser.add_argument(
- "--websocket_timeout",
- type=int,
- help="Maximum time to allow a websocket to be connected. -1 for infinite.",
- default=86400,
- )
- self.parser.add_argument(
- "--websocket_connect_timeout",
- type=int,
- help="Maximum time to allow a connection to handshake. -1 for infinite",
- default=5,
- )
- self.parser.add_argument(
- "-u",
- "--unix-socket",
- dest="unix_socket",
- help="Bind to a UNIX socket rather than a TCP host/port",
- default=None,
- )
- self.parser.add_argument(
- "--fd",
- type=int,
- dest="file_descriptor",
- help="Bind to a file descriptor rather than a TCP host/port or named unix socket",
- default=None,
- )
- self.parser.add_argument(
- "-e",
- "--endpoint",
- dest="socket_strings",
- action="append",
- help="Use raw server strings passed directly to twisted",
- default=[],
- )
- self.parser.add_argument(
- "-v",
- "--verbosity",
- type=int,
- help="How verbose to make the output",
- default=1,
- )
- self.parser.add_argument(
- "-t",
- "--http-timeout",
- type=int,
- help="How long to wait for worker before timing out HTTP connections",
- default=None,
- )
- self.parser.add_argument(
- "--access-log",
- help="Where to write the access log (- for stdout, the default for verbosity=1)",
- default=None,
- )
- self.parser.add_argument(
- "--log-fmt",
- help="Log format to use",
- default="%(asctime)-15s %(levelname)-8s %(message)s",
- )
- self.parser.add_argument(
- "--ping-interval",
- type=int,
- help="The number of seconds a WebSocket must be idle before a keepalive ping is sent",
- default=20,
- )
- self.parser.add_argument(
- "--ping-timeout",
- type=int,
- help="The number of seconds before a WebSocket is closed if no response to a keepalive ping",
- default=30,
- )
- self.parser.add_argument(
- "--application-close-timeout",
- type=int,
- help="The number of seconds an ASGI application has to exit after client disconnect before it is killed",
- default=10,
- )
- self.parser.add_argument(
- "--root-path",
- dest="root_path",
- help="The setting for the ASGI root_path variable",
- default="",
- )
- self.parser.add_argument(
- "--proxy-headers",
- dest="proxy_headers",
- help="Enable parsing and using of X-Forwarded-For and X-Forwarded-Port headers and using that as the "
- "client address",
- default=False,
- action="store_true",
- )
- self.arg_proxy_host = self.parser.add_argument(
- "--proxy-headers-host",
- dest="proxy_headers_host",
- help="Specify which header will be used for getting the host "
- "part. Can be omitted, requires --proxy-headers to be specified "
- 'when passed. "X-Real-IP" (when passed by your webserver) is a '
- "good candidate for this.",
- default=False,
- action="store",
- )
- self.arg_proxy_port = self.parser.add_argument(
- "--proxy-headers-port",
- dest="proxy_headers_port",
- help="Specify which header will be used for getting the port "
- "part. Can be omitted, requires --proxy-headers to be specified "
- "when passed.",
- default=False,
- action="store",
- )
- self.parser.add_argument(
- "application",
- help="The application to dispatch to as path.to.module:instance.path",
- )
- self.parser.add_argument(
- "-s",
- "--server-name",
- dest="server_name",
- help="specify which value should be passed to response header Server attribute",
- default="daphne",
- )
- self.parser.add_argument(
- "--no-server-name", dest="server_name", action="store_const", const=""
- )
-
- self.server = None
-
- @classmethod
- def entrypoint(cls):
- """
- Main entrypoint for external starts.
- """
- cls().run(sys.argv[1:])
-
- def _check_proxy_headers_passed(self, argument: str, args: Namespace):
- """Raise if the `--proxy-headers` weren't specified."""
- if args.proxy_headers:
- return
- raise ArgumentError(
- argument=argument,
- message="--proxy-headers has to be passed for this parameter.",
- )
-
- def _get_forwarded_host(self, args: Namespace):
- """
- Return the default host header from which the remote hostname/ip
- will be extracted.
- """
- if args.proxy_headers_host:
- self._check_proxy_headers_passed(argument=self.arg_proxy_host, args=args)
- return args.proxy_headers_host
- if args.proxy_headers:
- return "X-Forwarded-For"
-
- def _get_forwarded_port(self, args: Namespace):
- """
- Return the default host header from which the remote hostname/ip
- will be extracted.
- """
- if args.proxy_headers_port:
- self._check_proxy_headers_passed(argument=self.arg_proxy_port, args=args)
- return args.proxy_headers_port
- if args.proxy_headers:
- return "X-Forwarded-Port"
-
- def run(self, args):
- """
- Pass in raw argument list and it will decode them
- and run the server.
- """
- # Decode args
- args = self.parser.parse_args(args)
- # Set up logging
- logging.basicConfig(
- level={
- 0: logging.WARN,
- 1: logging.INFO,
- 2: logging.DEBUG,
- 3: logging.DEBUG, # Also turns on asyncio debug
- }[args.verbosity],
- format=args.log_fmt,
- )
- # If verbosity is 1 or greater, or they told us explicitly, set up access log
- access_log_stream = None
- if args.access_log:
- if args.access_log == "-":
- access_log_stream = sys.stdout
- else:
- access_log_stream = open(args.access_log, "a", 1)
- elif args.verbosity >= 1:
- access_log_stream = sys.stdout
-
- # Import application
- sys.path.insert(0, ".")
- application = import_by_path(args.application)
- application = guarantee_single_callable(application)
-
- # Set up port/host bindings
- if not any(
- [
- args.host,
- args.port is not None,
- args.unix_socket,
- args.file_descriptor is not None,
- args.socket_strings,
- ]
- ):
- # no advanced binding options passed, patch in defaults
- args.host = DEFAULT_HOST
- args.port = DEFAULT_PORT
- elif args.host and args.port is None:
- args.port = DEFAULT_PORT
- elif args.port is not None and not args.host:
- args.host = DEFAULT_HOST
- # Build endpoint description strings from (optional) cli arguments
- endpoints = build_endpoint_description_strings(
- host=args.host,
- port=args.port,
- unix_socket=args.unix_socket,
- file_descriptor=args.file_descriptor,
- )
- endpoints = sorted(args.socket_strings + endpoints)
- # Start the server
- logger.info("Starting server at {}".format(", ".join(endpoints)))
- self.server = self.server_class(
- application=application,
- endpoints=endpoints,
- http_timeout=args.http_timeout,
- ping_interval=args.ping_interval,
- ping_timeout=args.ping_timeout,
- websocket_timeout=args.websocket_timeout,
- websocket_connect_timeout=args.websocket_connect_timeout,
- websocket_handshake_timeout=args.websocket_connect_timeout,
- application_close_timeout=args.application_close_timeout,
- action_logger=AccessLogGenerator(access_log_stream)
- if access_log_stream
- else None,
- root_path=args.root_path,
- verbosity=args.verbosity,
- proxy_forwarded_address_header=self._get_forwarded_host(args=args),
- proxy_forwarded_port_header=self._get_forwarded_port(args=args),
- proxy_forwarded_proto_header="X-Forwarded-Proto"
- if args.proxy_headers
- else None,
- server_name=args.server_name,
- )
- self.server.run()
|