mirror of
https://github.com/django/daphne.git
synced 2025-04-20 08:42:18 +03:00
Support for HTTPS(secure) connection
This commit is contained in:
parent
c71a035004
commit
04b2cc6b4c
12
README.rst
12
README.rst
|
@ -55,3 +55,15 @@ The header takes precedence if both are set. As with ``SCRIPT_ALIAS``, the value
|
|||
should start with a slash, but not end with one; for example::
|
||||
|
||||
daphne --root-path=/forum django_project.asgi:channel_layer
|
||||
|
||||
|
||||
|
||||
Running daphne in secure mode
|
||||
-----------------------------
|
||||
|
||||
Simply copy the key and certificate to the certificates folder inside the daphne path as server.key and server.crt or add path of the certificate as arguments. Specify secure connection as argument along with it, point Daphne to your ASGI channel layer instance, and optionally
|
||||
set a bind address and port (defaults to localhost, port 8000) add options ::
|
||||
|
||||
daphne -b 0.0.0.0 -p 8001 django_project.asgi:channel_layer -secure True -cert <path to SSL certificate> -key <path to SSL key>
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ import sys
|
|||
import argparse
|
||||
import logging
|
||||
import importlib
|
||||
import os
|
||||
|
||||
from .server import Server
|
||||
from .access import AccessLogGenerator
|
||||
|
||||
|
@ -27,6 +29,28 @@ class CommandLineInterface(object):
|
|||
help='Port number to listen on',
|
||||
default=8000,
|
||||
)
|
||||
self.parser.add_argument(
|
||||
'-secure',
|
||||
'--secure',
|
||||
type= bool,
|
||||
dest= 'is_secure',
|
||||
help='Set it to True to make secure connection',
|
||||
default=False,
|
||||
)
|
||||
self.parser.add_argument(
|
||||
'-cert',
|
||||
'--certificate',
|
||||
dest= 'certificate_path',
|
||||
help='Setthe path to the SSL certificate for secure connection',
|
||||
default=os.path.dirname(__file__) +"/certificates/server.crt",
|
||||
)
|
||||
self.parser.add_argument(
|
||||
'-key',
|
||||
'--key',
|
||||
dest= 'key_path',
|
||||
help='Set the path to the SSL key for secure connection',
|
||||
default=os.path.dirname(__file__) +"/certificates/server.key",
|
||||
)
|
||||
self.parser.add_argument(
|
||||
'-b',
|
||||
'--bind',
|
||||
|
@ -34,6 +58,12 @@ class CommandLineInterface(object):
|
|||
help='The host/address to bind to',
|
||||
default="127.0.0.1",
|
||||
)
|
||||
self.parser.add_argument(
|
||||
'--ping-timeout',
|
||||
type=int,
|
||||
help='The number of seconds before a WeSocket is closed if no response to a keepalive ping',
|
||||
default=30,
|
||||
)
|
||||
self.parser.add_argument(
|
||||
'-u',
|
||||
'--unix-socket',
|
||||
|
@ -73,12 +103,6 @@ class CommandLineInterface(object):
|
|||
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 WeSocket is closed if no response to a keepalive ping',
|
||||
default=30,
|
||||
)
|
||||
self.parser.add_argument(
|
||||
'channel_layer',
|
||||
help='The ASGI channel layer instance to use as path.to.module:instance.path',
|
||||
|
@ -153,4 +177,7 @@ class CommandLineInterface(object):
|
|||
action_logger=AccessLogGenerator(access_log_stream) if access_log_stream else None,
|
||||
ws_protocols=args.ws_protocols,
|
||||
root_path=args.root_path,
|
||||
secure=args.is_secure,
|
||||
certificate = args.certificate_path,
|
||||
key = args.key_path
|
||||
).run()
|
||||
|
|
182
daphne/cli.py~
Executable file
182
daphne/cli.py~
Executable file
|
@ -0,0 +1,182 @@
|
|||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
import importlib
|
||||
import os
|
||||
|
||||
from .server import Server
|
||||
from .access import AccessLogGenerator
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CommandLineInterface(object):
|
||||
"""
|
||||
Acts as the main CLI entry point for running the server.
|
||||
"""
|
||||
|
||||
description = "Django HTTP/WebSocket 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=8000,
|
||||
)
|
||||
self.parser.add_argument(
|
||||
'-secure',
|
||||
'--secure',
|
||||
type= bool,
|
||||
dest= 'is_secure',
|
||||
help='Set it to True to make secure connection',
|
||||
default=False,
|
||||
)
|
||||
self.parser.add_argument(
|
||||
'-cert',
|
||||
'--certificate',
|
||||
dest= 'certificate_path',
|
||||
help='Setthe path to the SSL certificate for secure connection',
|
||||
default=os.path.dirname(__file__) +"/certificates/server.crt",
|
||||
)
|
||||
self.parser.add_argument(
|
||||
'-key',
|
||||
'--key',
|
||||
dest= 'key_path',
|
||||
help='Set the path to the SSL key for secure connection',
|
||||
default=os.path.dirname(__file__) +"/certificates/server.key",
|
||||
)
|
||||
self.parser.add_argument(
|
||||
'-b',
|
||||
'--bind',
|
||||
dest='host',
|
||||
help='The host/address to bind to',
|
||||
default="127.0.0.1",
|
||||
)
|
||||
self.parser.add_argument(
|
||||
'--ping-timeout',
|
||||
type=int,
|
||||
help='The number of seconds before a WeSocket is closed if no response to a keepalive ping',
|
||||
default=30,
|
||||
)
|
||||
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(
|
||||
'-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 server before timing out HTTP connections',
|
||||
default=120,
|
||||
)
|
||||
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(
|
||||
'channel_layer',
|
||||
help='The ASGI channel layer instance to use as path.to.module:instance.path',
|
||||
)
|
||||
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="",
|
||||
)
|
||||
|
||||
@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,
|
||||
}[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")
|
||||
elif args.verbosity >= 1:
|
||||
access_log_stream = sys.stdout
|
||||
# Import channel layer
|
||||
sys.path.insert(0, ".")
|
||||
module_path, object_path = args.channel_layer.split(":", 1)
|
||||
channel_layer = importlib.import_module(module_path)
|
||||
for bit in object_path.split("."):
|
||||
channel_layer = getattr(channel_layer, bit)
|
||||
# Run server
|
||||
logger.info(
|
||||
"Starting server at %s, channel layer %s",
|
||||
(args.unix_socket if args.unix_socket else "%s:%s" % (args.host, args.port)),
|
||||
args.channel_layer,
|
||||
)
|
||||
Server(
|
||||
channel_layer=channel_layer,
|
||||
host=args.host,
|
||||
port=args.port,
|
||||
unix_socket=args.unix_socket,
|
||||
file_descriptor=args.file_descriptor,
|
||||
http_timeout=args.http_timeout,
|
||||
ping_interval=args.ping_interval,
|
||||
action_logger=AccessLogGenerator(access_log_stream) if access_log_stream else None,
|
||||
ws_protocols=args.ws_protocols,
|
||||
root_path=args.root_path,
|
||||
secure=args.is_secure,
|
||||
certificate = args.certificate_path,
|
||||
key = args.key_path
|
||||
).run()
|
|
@ -1,7 +1,8 @@
|
|||
import logging
|
||||
import socket
|
||||
import os
|
||||
|
||||
from twisted.internet import reactor, defer
|
||||
from twisted.internet import reactor, defer, ssl
|
||||
from twisted.logger import globalLogBeginner
|
||||
|
||||
from .http_protocol import HTTPFactory
|
||||
|
@ -23,9 +24,11 @@ class Server(object):
|
|||
http_timeout=120,
|
||||
websocket_timeout=None,
|
||||
ping_interval=20,
|
||||
ping_timeout=30,
|
||||
ws_protocols=None,
|
||||
root_path="",
|
||||
secure=False,
|
||||
certificate = os.path.dirname(__file__) +"/certificates/server.crt",
|
||||
key = os.path.dirname(__file__) +"/certificates/server.key",
|
||||
):
|
||||
self.channel_layer = channel_layer
|
||||
self.host = host
|
||||
|
@ -42,6 +45,9 @@ class Server(object):
|
|||
self.websocket_timeout = websocket_timeout or getattr(channel_layer, "group_expiry", 86400)
|
||||
self.ws_protocols = ws_protocols
|
||||
self.root_path = root_path
|
||||
self.secure = secure
|
||||
self.certificate = certificate
|
||||
self.key = key
|
||||
|
||||
def run(self):
|
||||
self.factory = HTTPFactory(
|
||||
|
@ -64,7 +70,18 @@ class Server(object):
|
|||
sock = socket.socket(fileno=self.file_descriptor)
|
||||
reactor.adoptStreamPort(self.file_descriptor, sock.family, self.factory)
|
||||
else:
|
||||
reactor.listenTCP(self.port, self.factory, interface=self.host)
|
||||
#secure connection request check
|
||||
if self.secure :
|
||||
|
||||
if os.path.isfile(self.key) and os.path.isfile(self.certificate):
|
||||
|
||||
reactor.listenSSL(self.port, self.factory, ssl.DefaultOpenSSLContextFactory(self.key, self.certificate),interface=self.host)
|
||||
|
||||
else :
|
||||
logging.error("SSL key and certificate are not properly configured. \n It should be placed in " +os.path.dirname(__file__) +"/certificates " + "folder as server.key and server.crt. \n Or you have to pass key and certificate path in -key and -cert arguments along with secure argument." )
|
||||
|
||||
else :
|
||||
reactor.listenTCP(self.port, self.factory, interface=self.host)
|
||||
|
||||
if "twisted" in self.channel_layer.extensions:
|
||||
logging.info("Using native Twisted mode on channel layer")
|
||||
|
|
147
daphne/server.py~
Executable file
147
daphne/server.py~
Executable file
|
@ -0,0 +1,147 @@
|
|||
import logging
|
||||
import socket
|
||||
import os
|
||||
|
||||
from twisted.internet import reactor, defer, ssl
|
||||
from twisted.logger import globalLogBeginner
|
||||
|
||||
from .http_protocol import HTTPFactory
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Server(object):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
channel_layer,
|
||||
host="127.0.0.1",
|
||||
port=8000,
|
||||
unix_socket=None,
|
||||
file_descriptor=None,
|
||||
signal_handlers=True,
|
||||
action_logger=None,
|
||||
http_timeout=120,
|
||||
websocket_timeout=None,
|
||||
ping_interval=20,
|
||||
ws_protocols=None,
|
||||
root_path="",
|
||||
secure=False,
|
||||
certificate = os.path.dirname(__file__) +"/certificates/server.crt",
|
||||
key = os.path.dirname(__file__) +"/certificates/server.key",
|
||||
):
|
||||
self.channel_layer = channel_layer
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.unix_socket = unix_socket
|
||||
self.file_descriptor = file_descriptor
|
||||
self.signal_handlers = signal_handlers
|
||||
self.action_logger = action_logger
|
||||
self.http_timeout = http_timeout
|
||||
self.ping_interval = ping_interval
|
||||
self.ping_timeout = ping_timeout
|
||||
# If they did not provide a websocket timeout, default it to the
|
||||
# channel layer's group_expiry value if present, or one day if not.
|
||||
self.websocket_timeout = websocket_timeout or getattr(channel_layer, "group_expiry", 86400)
|
||||
self.ws_protocols = ws_protocols
|
||||
self.root_path = root_path
|
||||
self.secure = secure
|
||||
self.certificate = certificate
|
||||
self.key = key
|
||||
|
||||
def run(self):
|
||||
self.factory = HTTPFactory(
|
||||
self.channel_layer,
|
||||
self.action_logger,
|
||||
timeout=self.http_timeout,
|
||||
websocket_timeout=self.websocket_timeout,
|
||||
ping_interval=self.ping_interval,
|
||||
ws_protocols=self.ws_protocols,
|
||||
root_path=self.root_path,
|
||||
)
|
||||
# Redirect the Twisted log to nowhere
|
||||
globalLogBeginner.beginLoggingTo([lambda _: None], redirectStandardIO=False, discardBuffer=True)
|
||||
# Listen on a socket
|
||||
if self.unix_socket:
|
||||
reactor.listenUNIX(self.unix_socket, self.factory)
|
||||
elif self.file_descriptor:
|
||||
# socket returns the same socket if supplied with a fileno
|
||||
sock = socket.socket(fileno=self.file_descriptor)
|
||||
reactor.adoptStreamPort(self.file_descriptor, sock.family, self.factory)
|
||||
else:
|
||||
#secure connection request check
|
||||
if self.secure :
|
||||
|
||||
if os.path.isfile(self.key) and os.path.isfile(self.certificate):
|
||||
|
||||
reactor.listenSSL(self.port, self.factory, ssl.DefaultOpenSSLContextFactory(self.key, self.certificate),interface=self.host)
|
||||
|
||||
else :
|
||||
logging.error("SSL key and certificate are not properly configured. \n It should be placed in " +os.path.dirname(__file__) +"/certificates " + "folder as server.key and server.crt. \n Or you have to pass key and certificate path in -key and -cert arguments along with secure argument." )
|
||||
|
||||
else :
|
||||
reactor.listenTCP(self.port, self.factory, interface=self.host)
|
||||
|
||||
if "twisted" in self.channel_layer.extensions:
|
||||
logging.info("Using native Twisted mode on channel layer")
|
||||
reactor.callLater(0, self.backend_reader_twisted)
|
||||
else:
|
||||
logging.info("Using busy-loop synchronous mode on channel layer")
|
||||
reactor.callLater(0, self.backend_reader_sync)
|
||||
reactor.callLater(2, self.timeout_checker)
|
||||
reactor.run(installSignalHandlers=self.signal_handlers)
|
||||
|
||||
def backend_reader_sync(self):
|
||||
"""
|
||||
Runs as an-often-as-possible task with the reactor, unless there was
|
||||
no result previously in which case we add a small delay.
|
||||
"""
|
||||
channels = self.factory.reply_channels()
|
||||
delay = 0.05
|
||||
# Quit if reactor is stopping
|
||||
if not reactor.running:
|
||||
logging.debug("Backend reader quitting due to reactor stop")
|
||||
return
|
||||
# Don't do anything if there's no channels to listen on
|
||||
if channels:
|
||||
delay = 0.01
|
||||
channel, message = self.channel_layer.receive_many(channels, block=False)
|
||||
if channel:
|
||||
delay = 0.00
|
||||
# Deal with the message
|
||||
self.factory.dispatch_reply(channel, message)
|
||||
reactor.callLater(delay, self.backend_reader_sync)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def backend_reader_twisted(self):
|
||||
"""
|
||||
Runs as an-often-as-possible task with the reactor, unless there was
|
||||
no result previously in which case we add a small delay.
|
||||
"""
|
||||
while True:
|
||||
if not reactor.running:
|
||||
logging.debug("Backend reader quitting due to reactor stop")
|
||||
return
|
||||
channels = self.factory.reply_channels()
|
||||
if channels:
|
||||
channel, message = yield self.channel_layer.receive_many_twisted(channels)
|
||||
# Deal with the message
|
||||
if channel:
|
||||
self.factory.dispatch_reply(channel, message)
|
||||
else:
|
||||
yield self.sleep(0.01)
|
||||
else:
|
||||
yield self.sleep(0.05)
|
||||
|
||||
def sleep(self, delay):
|
||||
d = defer.Deferred()
|
||||
reactor.callLater(delay, d.callback, None)
|
||||
return d
|
||||
|
||||
def timeout_checker(self):
|
||||
"""
|
||||
Called periodically to enforce timeout rules on all connections.
|
||||
Also checks pings at the same time.
|
||||
"""
|
||||
self.factory.check_timeouts()
|
||||
reactor.callLater(2, self.timeout_checker)
|
Loading…
Reference in New Issue
Block a user