Skip to content

Commit e978361

Browse files
committed
Add websocket subprotocol support
## Rationale: We have to manually do this currently even while it should be part of the handshake. This leads to ignorance of this feature, and chrome not working due to a missing header when testing only in firefox (which ignores if none of sent subprotocols come back) ## Implementation and usage: I’m adding a kwarg to `do_handshake` which allows the user to specifiy subprotocols his/her client speaks. `do_handshake` returns an additional header with the chosen protocol if the handshake is successful. This doesn’t break the API in any way. If the server speaks any of the supplied subprotocols, the first protocol spoken by both is selected from the the client-supplied list, else the handshake fails. Essentially this means that you order the `protocols` sequence by preference: best protocol first, worst last. ## Open Questions: 1. The agreed-upon protocol is valuable information not only for the Server, but the user: If we speak multiple protocols, we want to know which one the handshake selected. The user has to extract it from the headers, which are simply a sequence and no dict, making this impractical. Should we change the response tuple to include the protocol as fifth mamber? This would break unpacking assignments like this: ```python >>> code, response_headers, parser, writer = do_handshake('get', headers, transport) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: too many values to unpack (expected 4) ``` We could also return something unpackable to 4 elemets that also has a “protocol” field, but that would make the code more complex (tuples are very useful to avoid creating Java-style result-classes). 2. Autobahn|Python forbids duplicate mentions of protocols in the comma-separated list sent by the server. Should we also fail the handshake when this happens or be more lenient?
1 parent 3df2141 commit e978361

File tree

1 file changed

+31
-7
lines changed

1 file changed

+31
-7
lines changed

aiohttp/websocket.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
WS_KEY = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
2323
WS_HDRS = ('UPGRADE', 'CONNECTION',
24-
'SEC-WEBSOCKET-VERSION', 'SEC-WEBSOCKET-KEY')
24+
'SEC-WEBSOCKET-VERSION', 'SEC-WEBSOCKET-KEY', 'SEC-WEBSOCKET-PROTOCOL')
2525

2626
Message = collections.namedtuple('Message', ['tp', 'data', 'extra'])
2727

@@ -182,10 +182,14 @@ def close(self, code=1000, message=b''):
182182
opcode=OPCODE_CLOSE)
183183

184184

185-
def do_handshake(method, headers, transport):
185+
def do_handshake(method, headers, transport, protocols=()):
186186
"""Prepare WebSocket handshake. It return http response code,
187187
response headers, websocket parser, websocket writer. It does not
188-
perform any IO."""
188+
perform any IO.
189+
190+
`protocols` is a sequence of known protocols. On successful handshake,
191+
the returned response headers contain the first protocol in this list
192+
which the server also knows."""
189193

190194
# WebSocket accepts only GET
191195
if method.upper() != 'GET':
@@ -201,6 +205,21 @@ def do_handshake(method, headers, transport):
201205
raise errors.HttpBadRequest(
202206
'No CONNECTION upgrade hdr: {}'.format(
203207
headers.get('CONNECTION')))
208+
209+
# find common sub-protocol between client and server
210+
protocol = None
211+
if 'SEC-WEBSOCKET-PROTOCOL' in headers:
212+
req_protocols = {str(proto.strip()) for proto in
213+
headers['SEC-WEBSOCKET-PROTOCOL'].split(',')}
214+
215+
for proto in protocols:
216+
if proto in req_protocols:
217+
protocol = proto
218+
break
219+
else:
220+
raise errors.HttpBadRequest(
221+
'Client protocols {} don’t overlap server-known ones {}'
222+
.format(protocols, req_protocols))
204223

205224
# check supported version
206225
version = headers.get('SEC-WEBSOCKET-VERSION')
@@ -218,12 +237,17 @@ def do_handshake(method, headers, transport):
218237
raise errors.HttpBadRequest(
219238
'Handshake error: {!r}'.format(key)) from None
220239

221-
# response code, headers, parser, writer
222-
return (101,
223-
(('UPGRADE', 'websocket'),
240+
response_headers = [('UPGRADE', 'websocket'),
224241
('CONNECTION', 'upgrade'),
225242
('TRANSFER-ENCODING', 'chunked'),
226243
('SEC-WEBSOCKET-ACCEPT', base64.b64encode(
227-
hashlib.sha1(key.encode() + WS_KEY).digest()).decode())),
244+
hashlib.sha1(key.encode() + WS_KEY).digest()).decode())]
245+
246+
if protocol:
247+
response_headers.append(('SEC-WEBSOCKET-PROTOCOL', protocol))
248+
249+
# response code, headers, parser, writer
250+
return (101,
251+
response_headers,
228252
WebSocketParser,
229253
WebSocketWriter(transport))

0 commit comments

Comments
 (0)