Support for HTTPS(secure) connection

This commit is contained in:
faseela 2016-08-08 13:18:04 +05:30
parent c71a035004
commit 04b2cc6b4c
5 changed files with 394 additions and 9 deletions

View File

@ -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>

View File

@ -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
View 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()

View File

@ -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
View 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)