Skip to content

Commit

Permalink
Added permessage-deflate support (backported from django#331)
Browse files Browse the repository at this point in the history
  • Loading branch information
encetamasb committed Dec 13, 2021
1 parent 9838a17 commit 4af1bd9
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 5 deletions.
15 changes: 15 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,21 @@ should start with a slash, but not end with one; for example::
daphne --root-path=/forum django_project.asgi:application


Permessage compression
----------------------

Daphne supports and by default accepts ``permessage-deflate`` compression
(`permessage-deflate specification <http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression>`_).
Additional ``permessage-bzip2``, ``permessage-snappy`` compressions will be also enabled by default if
``bz2`` and `snappy <https://snappy.math.uic.edu/>`_ python packages are available in daphne environment.
The compression implementation is provided by
`Autobahn|Python <https://github.com/crossbario/autobahn-python>`_ package, see:
`permessage-deflate <https://github.com/crossbario/autobahn-python/blob/master/autobahn/websocket/compress_deflate.py>`_,
`permessage-bzip2 <https://github.com/crossbario/autobahn-python/blob/master/autobahn/websocket/compress_bzip2.py>`_,
`permessage-snappy <https://github.com/crossbario/autobahn-python/blob/master/autobahn/websocket/compress_snappy.py>`_.



Python Support
--------------

Expand Down
25 changes: 25 additions & 0 deletions daphne/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import time
from concurrent.futures import CancelledError

from autobahn.websocket.compress import PERMESSAGE_COMPRESSION_EXTENSION as EXTENSIONS
from twisted.internet import defer, reactor
from twisted.internet.endpoints import serverFromString
from twisted.logger import STDLibLogObserver, globalLogBeginner
Expand All @@ -44,6 +45,11 @@ def __init__(
http_timeout=None,
websocket_timeout=86400,
websocket_connect_timeout=20,
websocket_permessage_compression_extensions=(
"permessage-deflate",
"permessage-bzip2",
"permessage-snappy",
),
ping_interval=20,
ping_timeout=30,
root_path="",
Expand Down Expand Up @@ -73,6 +79,9 @@ def __init__(
self.websocket_timeout = websocket_timeout
self.websocket_connect_timeout = websocket_connect_timeout
self.websocket_handshake_timeout = websocket_handshake_timeout
self.websocket_permessage_compression_extensions = (
websocket_permessage_compression_extensions
)
self.application_close_timeout = application_close_timeout
self.root_path = root_path
self.verbosity = verbosity
Expand All @@ -94,6 +103,7 @@ def run(self):
autoPingTimeout=self.ping_timeout,
allowNullOrigin=True,
openHandshakeTimeout=self.websocket_handshake_timeout,
perMessageCompressionAccept=self.accept_permessage_compression_extension,
)
if self.verbosity <= 1:
# Redirect the Twisted log to nowhere
Expand Down Expand Up @@ -246,6 +256,21 @@ def check_headers_type(message):
)
)

def accept_permessage_compression_extension(self, offers):
"""
Accepts websocket compression extension as required by `autobahn` package.
"""
for offer in offers:
for ext in self.websocket_permessage_compression_extensions:
if ext in EXTENSIONS and isinstance(offer, EXTENSIONS[ext]["Offer"]):
return EXTENSIONS[ext]["OfferAccept"](offer)
elif ext not in EXTENSIONS:
logger.warning(
"Compression extension %s could not be accepted. "
"It is not supported or a dependency is missing.",
ext,
)

### Utility

def application_checker(self):
Expand Down
14 changes: 9 additions & 5 deletions daphne/ws_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ def handle_reply(self, message):
if "type" not in message:
raise ValueError("Message has no type defined")
if message["type"] == "websocket.accept":
self.serverAccept(message.get("subprotocol", None))
self.serverAccept(message.get("subprotocol", None), message.get("headers", None))
elif message["type"] == "websocket.close":
if self.state == self.STATE_CONNECTING:
self.serverReject()
Expand Down Expand Up @@ -214,13 +214,17 @@ def handle_exception(self, exception):
else:
self.sendCloseFrame(code=1011)

def serverAccept(self, subprotocol=None):
def serverAccept(self, subprotocol=None, headers=None):
"""
Called when we get a message saying to accept the connection.
"""
self.handshake_deferred.callback(subprotocol)
del self.handshake_deferred
logger.debug("WebSocket %s accepted by application", self.client_addr)
if headers is None:
self.handshake_deferred.callback(subprotocol)
else:
headers_dict = {key.decode(): value.decode() for key, value in headers}
self.handshake_deferred.callback((subprotocol, headers_dict))
del self.handshake_deferred
logger.debug("WebSocket %s accepted by application", self.client_addr)

def serverReject(self):
"""
Expand Down

0 comments on commit 4af1bd9

Please sign in to comment.