import argparse import logging import sys 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(object): """ 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( "--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( "--ws-protocol", nargs="*", dest="ws_protocols", help="The WebSocket protocols you wish to support", default=None, ) 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.parser.add_argument( "application", help="The application to dispatch to as path.to.module:instance.path", ) self.server = None @classmethod def entrypoint(cls): """ Main entrypoint for external starts. """ cls().run(sys.argv[1:]) 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="%(asctime)-15s %(levelname)-8s %(message)s", ) # 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) # Set up port/host bindings if not any([args.host, args.port is not None, args.unix_socket, args.file_descriptor, 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 %s" % (", ".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, application_close_timeout=args.application_close_timeout, action_logger=AccessLogGenerator(access_log_stream) if access_log_stream else None, ws_protocols=args.ws_protocols, root_path=args.root_path, verbosity=args.verbosity, proxy_forwarded_address_header="X-Forwarded-For" if args.proxy_headers else None, proxy_forwarded_port_header="X-Forwarded-Port" if args.proxy_headers else None, proxy_forwarded_proto_header="X-Forwarded-Proto" if args.proxy_headers else None, ) self.server.run()