Skip to content

Commit 2bbf251

Browse files
committed
Allow to accept websocket extensions django#331
From albertas:master
2 parents 6a50939 + 7854278 commit 2bbf251

File tree

4 files changed

+103
-3
lines changed

4 files changed

+103
-3
lines changed

README.rst

+14
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,20 @@ should start with a slash, but not end with one; for example::
105105
daphne --root-path=/forum django_project.asgi:application
106106

107107

108+
Permessage compression
109+
----------------------
110+
111+
Daphne supports and by default accepts ``permessage-deflate`` compression
112+
(`permessage-deflate specification <http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression>`_).
113+
Additional ``permessage-bzip2``, ``permessage-snappy`` compressions will be also enabled by default if
114+
``bz2`` and `snappy <https://snappy.math.uic.edu/>`_ python packages are available in daphne environment.
115+
The compression implementation is provided by
116+
`Autobahn|Python <https://github.com/crossbario/autobahn-python>`_ package, see:
117+
`permessage-deflate <https://github.com/crossbario/autobahn-python/blob/master/autobahn/websocket/compress_deflate.py>`_,
118+
`permessage-bzip2 <https://github.com/crossbario/autobahn-python/blob/master/autobahn/websocket/compress_bzip2.py>`_,
119+
`permessage-snappy <https://github.com/crossbario/autobahn-python/blob/master/autobahn/websocket/compress_snappy.py>`_.
120+
121+
108122
Python Support
109123
--------------
110124

daphne/server.py

+25
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from concurrent.futures import CancelledError
2525
from functools import partial
2626

27+
from autobahn.websocket.compress import PERMESSAGE_COMPRESSION_EXTENSION as EXTENSIONS
2728
from twisted.internet import defer, reactor
2829
from twisted.internet.endpoints import serverFromString
2930
from twisted.logger import STDLibLogObserver, globalLogBeginner
@@ -46,6 +47,11 @@ def __init__(
4647
request_buffer_size=8192,
4748
websocket_timeout=86400,
4849
websocket_connect_timeout=20,
50+
websocket_permessage_compression_extensions=(
51+
"permessage-deflate",
52+
"permessage-bzip2",
53+
"permessage-snappy",
54+
),
4955
ping_interval=20,
5056
ping_timeout=30,
5157
root_path="",
@@ -76,6 +82,9 @@ def __init__(
7682
self.websocket_timeout = websocket_timeout
7783
self.websocket_connect_timeout = websocket_connect_timeout
7884
self.websocket_handshake_timeout = websocket_handshake_timeout
85+
self.websocket_permessage_compression_extensions = (
86+
websocket_permessage_compression_extensions
87+
)
7988
self.application_close_timeout = application_close_timeout
8089
self.root_path = root_path
8190
self.verbosity = verbosity
@@ -97,6 +106,7 @@ def run(self):
97106
autoPingTimeout=self.ping_timeout,
98107
allowNullOrigin=True,
99108
openHandshakeTimeout=self.websocket_handshake_timeout,
109+
perMessageCompressionAccept=self.accept_permessage_compression_extension,
100110
)
101111
if self.verbosity <= 1:
102112
# Redirect the Twisted log to nowhere
@@ -251,6 +261,21 @@ def check_headers_type(message):
251261
)
252262
)
253263

264+
def accept_permessage_compression_extension(self, offers):
265+
"""
266+
Accepts websocket compression extension as required by `autobahn` package.
267+
"""
268+
for offer in offers:
269+
for ext in self.websocket_permessage_compression_extensions:
270+
if ext in EXTENSIONS and isinstance(offer, EXTENSIONS[ext]["Offer"]):
271+
return EXTENSIONS[ext]["OfferAccept"](offer)
272+
elif ext not in EXTENSIONS:
273+
logger.warning(
274+
"Compression extension %s could not be accepted. "
275+
"It is not supported or a dependency is missing.",
276+
ext,
277+
)
278+
254279
### Utility
255280

256281
def application_checker(self):

daphne/ws_protocol.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,10 @@ def handle_reply(self, message):
182182
if "type" not in message:
183183
raise ValueError("Message has no type defined")
184184
if message["type"] == "websocket.accept":
185-
self.serverAccept(message.get("subprotocol", None))
185+
self.serverAccept(
186+
message.get("subprotocol", None), message.get("headers", None)
187+
)
188+
186189
elif message["type"] == "websocket.close":
187190
if self.state == self.STATE_CONNECTING:
188191
self.serverReject()
@@ -214,11 +217,15 @@ def handle_exception(self, exception):
214217
else:
215218
self.sendCloseFrame(code=1011)
216219

217-
def serverAccept(self, subprotocol=None):
220+
def serverAccept(self, subprotocol=None, headers=None):
218221
"""
219222
Called when we get a message saying to accept the connection.
220223
"""
221-
self.handshake_deferred.callback(subprotocol)
224+
if headers is None:
225+
self.handshake_deferred.callback(subprotocol)
226+
else:
227+
headers_dict = {key.decode(): value.decode() for key, value in headers}
228+
self.handshake_deferred.callback((subprotocol, headers_dict))
222229
del self.handshake_deferred
223230
logger.debug("WebSocket %s accepted by application", self.client_addr)
224231

tests/test_websocket.py

+54
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,60 @@ def test_subprotocols(self):
139139
self.assert_valid_websocket_scope(scope, subprotocols=subprotocols)
140140
self.assert_valid_websocket_connect_message(messages[0])
141141

142+
def test_accept_permessage_deflate_extension(self):
143+
"""
144+
Tests that permessage-deflate extension is successfuly accepted
145+
by underlying `autobahn` package.
146+
"""
147+
148+
headers = [
149+
(
150+
b"Sec-WebSocket-Extensions",
151+
b"permessage-deflate; client_max_window_bits",
152+
),
153+
]
154+
155+
with DaphneTestingInstance() as test_app:
156+
test_app.add_send_messages(
157+
[
158+
{
159+
"type": "websocket.accept",
160+
}
161+
]
162+
)
163+
164+
sock, subprotocol = self.websocket_handshake(
165+
test_app,
166+
headers=headers,
167+
)
168+
# Validate the scope and messages we got
169+
scope, messages = test_app.get_received()
170+
self.assert_valid_websocket_connect_message(messages[0])
171+
172+
def test_accept_custom_extension(self):
173+
"""
174+
Tests that custom headers can be accpeted during handshake.
175+
"""
176+
with DaphneTestingInstance() as test_app:
177+
test_app.add_send_messages(
178+
[
179+
{
180+
"type": "websocket.accept",
181+
"headers": [(b"Sec-WebSocket-Extensions", b"custom-extension")],
182+
}
183+
]
184+
)
185+
186+
sock, subprotocol = self.websocket_handshake(
187+
test_app,
188+
headers=[
189+
(b"Sec-WebSocket-Extensions", b"custom-extension"),
190+
],
191+
)
192+
# Validate the scope and messages we got
193+
scope, messages = test_app.get_received()
194+
self.assert_valid_websocket_connect_message(messages[0])
195+
142196
def test_xff(self):
143197
"""
144198
Tests that X-Forwarded-For headers get parsed right

0 commit comments

Comments
 (0)