Skip to content

Commit 96a760d

Browse files
author
Nikolay Kim
authored
Merge pull request #1643 from armills/ws_connect_fix
Fix multiple ws_connect with headers dict
2 parents 55e1228 + 0b6681d commit 96a760d

File tree

3 files changed

+55
-3
lines changed

3 files changed

+55
-3
lines changed

CHANGES.rst

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
CHANGES
22
=======
33

4+
- Fix multiple calls to client ws_connect when using a shared header dict #1643
5+
46
1.3.1 (2017-02-09)
57
------------------
68

aiohttp/client.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -317,22 +317,22 @@ def _ws_connect(self, url, *,
317317
proxy=None,
318318
proxy_auth=None):
319319

320-
sec_key = base64.b64encode(os.urandom(16))
321-
322320
if headers is None:
323321
headers = CIMultiDict()
324322

325323
default_headers = {
326324
hdrs.UPGRADE: hdrs.WEBSOCKET,
327325
hdrs.CONNECTION: hdrs.UPGRADE,
328326
hdrs.SEC_WEBSOCKET_VERSION: '13',
329-
hdrs.SEC_WEBSOCKET_KEY: sec_key.decode(),
330327
}
331328

332329
for key, value in default_headers.items():
333330
if key not in headers:
334331
headers[key] = value
335332

333+
sec_key = base64.b64encode(os.urandom(16))
334+
headers[hdrs.SEC_WEBSOCKET_KEY] = sec_key.decode()
335+
336336
if protocols:
337337
headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = ','.join(protocols)
338338
if origin is not None:

tests/test_client_ws.py

+50
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,56 @@ def test_ws_connect_err_challenge(loop, ws_key, key_data):
215215
assert ctx.value.message == 'Invalid challenge response'
216216

217217

218+
@asyncio.coroutine
219+
def test_ws_connect_common_headers(ws_key, loop, key_data):
220+
"""Emulate a headers dict being reused for a second ws_connect.
221+
222+
In this scenario, we need to ensure that the newly generated secret key
223+
is sent to the server, not the stale key.
224+
"""
225+
headers = {}
226+
227+
@asyncio.coroutine
228+
def test_connection():
229+
@asyncio.coroutine
230+
def mock_get(*args, **kwargs):
231+
resp = mock.Mock()
232+
resp.status = 101
233+
key = kwargs.get('headers').get(hdrs.SEC_WEBSOCKET_KEY)
234+
accept = base64.b64encode(
235+
hashlib.sha1(base64.b64encode(base64.b64decode(key)) + WS_KEY)
236+
.digest()).decode()
237+
resp.headers = {
238+
hdrs.UPGRADE: hdrs.WEBSOCKET,
239+
hdrs.CONNECTION: hdrs.UPGRADE,
240+
hdrs.SEC_WEBSOCKET_ACCEPT: accept,
241+
hdrs.SEC_WEBSOCKET_PROTOCOL: 'chat'
242+
}
243+
return resp
244+
with mock.patch('aiohttp.client.os') as m_os:
245+
with mock.patch('aiohttp.client.ClientSession.get',
246+
side_effect=mock_get) as m_req:
247+
m_os.urandom.return_value = key_data
248+
#m_req.return_value = helpers.create_future(loop)
249+
#m_req.return_value.set_result(resp)
250+
251+
res = yield from aiohttp.ClientSession(loop=loop).ws_connect(
252+
'http://test.org',
253+
protocols=('t1', 't2', 'chat'),
254+
headers=headers)
255+
256+
assert isinstance(res, ClientWebSocketResponse)
257+
assert res.protocol == 'chat'
258+
assert hdrs.ORIGIN not in m_req.call_args[1]["headers"]
259+
260+
yield from test_connection()
261+
# Generate a new ws key
262+
key_data = os.urandom(16)
263+
ws_key = base64.b64encode(
264+
hashlib.sha1(base64.b64encode(key_data) + WS_KEY).digest()).decode()
265+
yield from test_connection()
266+
267+
218268
@asyncio.coroutine
219269
def test_close(loop, ws_key, key_data):
220270
resp = mock.Mock()

0 commit comments

Comments
 (0)