|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- import datetime
- import importlib
- import logging
- import sys
-
- from django.apps import apps
- from django.conf import settings
- from django.contrib.staticfiles.handlers import ASGIStaticFilesHandler
- from django.core.exceptions import ImproperlyConfigured
- from django.core.management import CommandError
- from django.core.management.commands.runserver import Command as RunserverCommand
-
- from daphne import __version__
- from daphne.endpoints import build_endpoint_description_strings
- from daphne.server import Server
-
- logger = logging.getLogger("django.channels.server")
-
-
- def get_default_application():
- """
- Gets the default application, set in the ASGI_APPLICATION setting.
- """
- try:
- path, name = settings.ASGI_APPLICATION.rsplit(".", 1)
- except (ValueError, AttributeError):
- raise ImproperlyConfigured("Cannot find ASGI_APPLICATION setting.")
- try:
- module = importlib.import_module(path)
- except ImportError:
- raise ImproperlyConfigured("Cannot import ASGI_APPLICATION module %r" % path)
- try:
- value = getattr(module, name)
- except AttributeError:
- raise ImproperlyConfigured(
- f"Cannot find {name!r} in ASGI_APPLICATION module {path}"
- )
- return value
-
-
- class Command(RunserverCommand):
- protocol = "http"
- server_cls = Server
-
- def add_arguments(self, parser):
- super().add_arguments(parser)
- parser.add_argument(
- "--noasgi",
- action="store_false",
- dest="use_asgi",
- default=True,
- help="Run the old WSGI-based runserver rather than the ASGI-based one",
- )
- parser.add_argument(
- "--http_timeout",
- action="store",
- dest="http_timeout",
- type=int,
- default=None,
- help=(
- "Specify the daphne http_timeout interval in seconds "
- "(default: no timeout)"
- ),
- )
- parser.add_argument(
- "--websocket_handshake_timeout",
- action="store",
- dest="websocket_handshake_timeout",
- type=int,
- default=5,
- help=(
- "Specify the daphne websocket_handshake_timeout interval in "
- "seconds (default: 5)"
- ),
- )
-
- def handle(self, *args, **options):
- self.http_timeout = options.get("http_timeout", None)
- self.websocket_handshake_timeout = options.get("websocket_handshake_timeout", 5)
- # Check Channels is installed right
- if options["use_asgi"] and not hasattr(settings, "ASGI_APPLICATION"):
- raise CommandError(
- "You have not set ASGI_APPLICATION, which is needed to run the server."
- )
- # Dispatch upward
- super().handle(*args, **options)
-
- def inner_run(self, *args, **options):
- # Maybe they want the wsgi one?
- if not options.get("use_asgi", True):
- if hasattr(RunserverCommand, "server_cls"):
- self.server_cls = RunserverCommand.server_cls
- return RunserverCommand.inner_run(self, *args, **options)
- # Run checks
- self.stdout.write("Performing system checks...\n\n")
- self.check(display_num_errors=True)
- self.check_migrations()
- # Print helpful text
- quit_command = "CTRL-BREAK" if sys.platform == "win32" else "CONTROL-C"
- now = datetime.datetime.now().strftime("%B %d, %Y - %X")
- self.stdout.write(now)
- self.stdout.write(
- (
- "Django version %(version)s, using settings %(settings)r\n"
- "Starting ASGI/Daphne version %(daphne_version)s development server"
- " at %(protocol)s://%(addr)s:%(port)s/\n"
- "Quit the server with %(quit_command)s.\n"
- )
- % {
- "version": self.get_version(),
- "daphne_version": __version__,
- "settings": settings.SETTINGS_MODULE,
- "protocol": self.protocol,
- "addr": "[%s]" % self.addr if self._raw_ipv6 else self.addr,
- "port": self.port,
- "quit_command": quit_command,
- }
- )
-
- # Launch server in 'main' thread. Signals are disabled as it's still
- # actually a subthread under the autoreloader.
- logger.debug("Daphne running, listening on %s:%s", self.addr, self.port)
-
- # build the endpoint description string from host/port options
- endpoints = build_endpoint_description_strings(host=self.addr, port=self.port)
- try:
- self.server_cls(
- application=self.get_application(options),
- endpoints=endpoints,
- signal_handlers=not options["use_reloader"],
- action_logger=self.log_action,
- http_timeout=self.http_timeout,
- root_path=getattr(settings, "FORCE_SCRIPT_NAME", "") or "",
- websocket_handshake_timeout=self.websocket_handshake_timeout,
- ).run()
- logger.debug("Daphne exited")
- except KeyboardInterrupt:
- shutdown_message = options.get("shutdown_message", "")
- if shutdown_message:
- self.stdout.write(shutdown_message)
- return
-
- def get_application(self, options):
- """
- Returns the static files serving application wrapping the default application,
- if static files should be served. Otherwise just returns the default
- handler.
- """
- staticfiles_installed = apps.is_installed("django.contrib.staticfiles")
- use_static_handler = options.get("use_static_handler", staticfiles_installed)
- insecure_serving = options.get("insecure_serving", False)
- if use_static_handler and (settings.DEBUG or insecure_serving):
- return ASGIStaticFilesHandler(get_default_application())
- else:
- return get_default_application()
-
- def log_action(self, protocol, action, details):
- """
- Logs various different kinds of requests to the console.
- """
- # HTTP requests
- if protocol == "http" and action == "complete":
- msg = "HTTP %(method)s %(path)s %(status)s [%(time_taken).2f, %(client)s]"
-
- # Utilize terminal colors, if available
- if 200 <= details["status"] < 300:
- # Put 2XX first, since it should be the common case
- logger.info(self.style.HTTP_SUCCESS(msg), details)
- elif 100 <= details["status"] < 200:
- logger.info(self.style.HTTP_INFO(msg), details)
- elif details["status"] == 304:
- logger.info(self.style.HTTP_NOT_MODIFIED(msg), details)
- elif 300 <= details["status"] < 400:
- logger.info(self.style.HTTP_REDIRECT(msg), details)
- elif details["status"] == 404:
- logger.warning(self.style.HTTP_NOT_FOUND(msg), details)
- elif 400 <= details["status"] < 500:
- logger.warning(self.style.HTTP_BAD_REQUEST(msg), details)
- else:
- # Any 5XX, or any other response
- logger.error(self.style.HTTP_SERVER_ERROR(msg), details)
-
- # Websocket requests
- elif protocol == "websocket" and action == "connected":
- logger.info("WebSocket CONNECT %(path)s [%(client)s]", details)
- elif protocol == "websocket" and action == "disconnected":
- logger.info("WebSocket DISCONNECT %(path)s [%(client)s]", details)
- elif protocol == "websocket" and action == "connecting":
- logger.info("WebSocket HANDSHAKING %(path)s [%(client)s]", details)
- elif protocol == "websocket" and action == "rejected":
- logger.info("WebSocket REJECT %(path)s [%(client)s]", details)
|