xref: /unit/test/test_asgi_websockets.py (revision 2616:ab2896c980ab)
1import struct
2import time
3
4import pytest
5from packaging import version
6
7from unit.applications.lang.python import ApplicationPython
8from unit.applications.websockets import ApplicationWebsocket
9
10prerequisites = {
11    'modules': {'python': lambda v: version.parse(v) >= version.parse('3.5')}
12}
13
14client = ApplicationPython(load_module='asgi')
15ws = ApplicationWebsocket()
16
17
18@pytest.fixture(autouse=True)
19def setup_method_fixture(skip_alert):
20    assert 'success' in client.conf(
21        {'http': {'websocket': {'keepalive_interval': 0}}}, 'settings'
22    ), 'clear keepalive_interval'
23
24    skip_alert(r'socket close\(\d+\) failed')
25
26
27def close_connection(sock):
28    assert client.recvall(sock, read_timeout=0.1) == b'', 'empty soc'
29
30    ws.frame_write(sock, ws.OP_CLOSE, ws.serialize_close())
31
32    check_close(sock)
33
34
35def check_close(sock, code=1000, no_close=False, frame=None):
36    if frame is None:
37        frame = ws.frame_read(sock)
38
39    assert frame['fin'], 'close fin'
40    assert frame['opcode'] == ws.OP_CLOSE, 'close opcode'
41    assert frame['code'] == code, 'close code'
42
43    if not no_close:
44        sock.close()
45
46
47def check_frame(frame, fin, opcode, payload, decode=True):
48    if opcode == ws.OP_BINARY or not decode:
49        data = frame['data']
50    else:
51        data = frame['data'].decode('utf-8')
52
53    assert frame['fin'] == fin, 'fin'
54    assert frame['opcode'] == opcode, 'opcode'
55    assert data == payload, 'payload'
56
57
58def test_asgi_websockets_handshake():
59    client.load('websockets/mirror')
60
61    resp, sock, key = ws.upgrade()
62    sock.close()
63
64    assert resp['status'] == 101, 'status'
65    assert resp['headers']['Upgrade'] == 'websocket', 'upgrade'
66    assert resp['headers']['Connection'] == 'Upgrade', 'connection'
67    assert resp['headers']['Sec-WebSocket-Accept'] == ws.accept(key), 'key'
68
69    # remove "mirror" application
70    client.load('websockets/subprotocol')
71
72
73def test_asgi_websockets_subprotocol():
74    client.load('websockets/subprotocol')
75
76    resp, sock, _ = ws.upgrade()
77    sock.close()
78
79    assert resp['status'] == 101, 'status'
80    assert (
81        resp['headers']['x-subprotocols'] == "('chat', 'phone', 'video')"
82    ), 'subprotocols'
83    assert resp['headers']['sec-websocket-protocol'] == 'chat', 'key'
84
85
86def test_asgi_websockets_mirror():
87    client.load('websockets/mirror')
88
89    message = 'blah'
90
91    _, sock, _ = ws.upgrade()
92
93    ws.frame_write(sock, ws.OP_TEXT, message)
94    frame = ws.frame_read(sock)
95
96    assert message == frame['data'].decode('utf-8'), 'mirror'
97
98    ws.frame_write(sock, ws.OP_TEXT, message)
99    frame = ws.frame_read(sock)
100
101    assert message == frame['data'].decode('utf-8'), 'mirror 2'
102
103    sock.close()
104
105
106def test_asgi_websockets_mirror_app_change():
107    client.load('websockets/mirror')
108
109    message = 'blah'
110
111    _, sock, _ = ws.upgrade()
112
113    ws.frame_write(sock, ws.OP_TEXT, message)
114    frame = ws.frame_read(sock)
115
116    assert message == frame['data'].decode('utf-8'), 'mirror'
117
118    client.load('websockets/subprotocol')
119
120    ws.frame_write(sock, ws.OP_TEXT, message)
121    frame = ws.frame_read(sock)
122
123    assert message == frame['data'].decode('utf-8'), 'mirror 2'
124
125    sock.close()
126
127
128def test_asgi_websockets_no_mask():
129    client.load('websockets/mirror')
130
131    message = 'blah'
132
133    _, sock, _ = ws.upgrade()
134
135    ws.frame_write(sock, ws.OP_TEXT, message, mask=False)
136
137    frame = ws.frame_read(sock)
138
139    assert frame['opcode'] == ws.OP_CLOSE, 'no mask opcode'
140    assert frame['code'] == 1002, 'no mask close code'
141
142    sock.close()
143
144
145def test_asgi_websockets_fragmentation():
146    client.load('websockets/mirror')
147
148    message = 'blah'
149
150    _, sock, _ = ws.upgrade()
151
152    ws.frame_write(sock, ws.OP_TEXT, message, fin=False)
153    ws.frame_write(sock, ws.OP_CONT, ' ', fin=False)
154    ws.frame_write(sock, ws.OP_CONT, message)
155
156    frame = ws.frame_read(sock)
157
158    assert f'{message} {message}' == frame['data'].decode(
159        'utf-8'
160    ), 'mirror framing'
161
162    sock.close()
163
164
165def test_asgi_websockets_length_long():
166    client.load('websockets/mirror')
167
168    _, sock, _ = ws.upgrade()
169
170    ws.frame_write(sock, ws.OP_TEXT, 'fragment1', fin=False)
171    ws.frame_write(sock, ws.OP_CONT, 'fragment2', length=2**64 - 1)
172
173    check_close(sock, 1009)  # 1009 - CLOSE_TOO_LARGE
174
175
176def test_asgi_websockets_frame_fragmentation_invalid():
177    client.load('websockets/mirror')
178
179    message = 'blah'
180
181    _, sock, _ = ws.upgrade()
182
183    ws.frame_write(sock, ws.OP_PING, message, fin=False)
184
185    frame = ws.frame_read(sock)
186
187    frame.pop('data')
188    assert frame == {
189        'fin': True,
190        'rsv1': False,
191        'rsv2': False,
192        'rsv3': False,
193        'opcode': ws.OP_CLOSE,
194        'mask': 0,
195        'code': 1002,
196        'reason': 'Fragmented control frame',
197    }, 'close frame'
198
199    sock.close()
200
201
202def test_asgi_websockets_large():
203    client.load('websockets/mirror')
204
205    message = '0123456789' * 300
206
207    _, sock, _ = ws.upgrade()
208
209    ws.frame_write(sock, ws.OP_TEXT, message)
210
211    frame = ws.frame_read(sock)
212    data = frame['data'].decode('utf-8')
213
214    frame = ws.frame_read(sock)
215    data += frame['data'].decode('utf-8')
216
217    assert message == data, 'large'
218
219    sock.close()
220
221
222def test_asgi_websockets_two_clients():
223    client.load('websockets/mirror')
224
225    message1 = 'blah1'
226    message2 = 'blah2'
227
228    _, sock1, _ = ws.upgrade()
229    _, sock2, _ = ws.upgrade()
230
231    ws.frame_write(sock1, ws.OP_TEXT, message1)
232    ws.frame_write(sock2, ws.OP_TEXT, message2)
233
234    frame1 = ws.frame_read(sock1)
235    frame2 = ws.frame_read(sock2)
236
237    assert message1 == frame1['data'].decode('utf-8'), 'client 1'
238    assert message2 == frame2['data'].decode('utf-8'), 'client 2'
239
240    sock1.close()
241    sock2.close()
242
243
244# FAIL https://tools.ietf.org/html/rfc6455#section-4.2.1
245@pytest.mark.skip('not yet')
246def test_asgi_websockets_handshake_upgrade_absent():
247    client.load('websockets/mirror')
248
249    resp = client.get(
250        headers={
251            'Host': 'localhost',
252            'Connection': 'Upgrade',
253            'Sec-WebSocket-Key': ws.key(),
254            'Sec-WebSocket-Protocol': 'chat',
255            'Sec-WebSocket-Version': 13,
256        },
257    )
258
259    assert resp['status'] == 400, 'upgrade absent'
260
261
262def test_asgi_websockets_handshake_case_insensitive():
263    client.load('websockets/mirror')
264
265    resp, sock, _ = ws.upgrade(
266        headers={
267            'Host': 'localhost',
268            'Upgrade': 'WEBSOCKET',
269            'Connection': 'UPGRADE',
270            'Sec-WebSocket-Key': ws.key(),
271            'Sec-WebSocket-Protocol': 'chat',
272            'Sec-WebSocket-Version': 13,
273        }
274    )
275    sock.close()
276
277    assert resp['status'] == 101, 'status'
278
279
280@pytest.mark.skip('not yet')
281def test_asgi_websockets_handshake_connection_absent():  # FAIL
282    client.load('websockets/mirror')
283
284    resp = client.get(
285        headers={
286            'Host': 'localhost',
287            'Upgrade': 'websocket',
288            'Sec-WebSocket-Key': ws.key(),
289            'Sec-WebSocket-Protocol': 'chat',
290            'Sec-WebSocket-Version': 13,
291        },
292    )
293
294    assert resp['status'] == 400, 'status'
295
296
297def test_asgi_websockets_handshake_version_absent():
298    client.load('websockets/mirror')
299
300    resp = client.get(
301        headers={
302            'Host': 'localhost',
303            'Upgrade': 'websocket',
304            'Connection': 'Upgrade',
305            'Sec-WebSocket-Key': ws.key(),
306            'Sec-WebSocket-Protocol': 'chat',
307        },
308    )
309
310    assert resp['status'] == 426, 'status'
311
312
313@pytest.mark.skip('not yet')
314def test_asgi_websockets_handshake_key_invalid():
315    client.load('websockets/mirror')
316
317    resp = client.get(
318        headers={
319            'Host': 'localhost',
320            'Upgrade': 'websocket',
321            'Connection': 'Upgrade',
322            'Sec-WebSocket-Key': '!',
323            'Sec-WebSocket-Protocol': 'chat',
324            'Sec-WebSocket-Version': 13,
325        },
326    )
327
328    assert resp['status'] == 400, 'key length'
329
330    key = ws.key()
331    resp = client.get(
332        headers={
333            'Host': 'localhost',
334            'Upgrade': 'websocket',
335            'Connection': 'Upgrade',
336            'Sec-WebSocket-Key': [key, key],
337            'Sec-WebSocket-Protocol': 'chat',
338            'Sec-WebSocket-Version': 13,
339        },
340    )
341
342    assert (
343        resp['status'] == 400
344    ), 'key double'  # FAIL https://tools.ietf.org/html/rfc6455#section-11.3.1
345
346
347def test_asgi_websockets_handshake_method_invalid():
348    client.load('websockets/mirror')
349
350    resp = client.post(
351        headers={
352            'Host': 'localhost',
353            'Upgrade': 'websocket',
354            'Connection': 'Upgrade',
355            'Sec-WebSocket-Key': ws.key(),
356            'Sec-WebSocket-Protocol': 'chat',
357            'Sec-WebSocket-Version': 13,
358        },
359    )
360
361    assert resp['status'] == 400, 'status'
362
363
364def test_asgi_websockets_handshake_http_10():
365    client.load('websockets/mirror')
366
367    resp = client.get(
368        headers={
369            'Host': 'localhost',
370            'Upgrade': 'websocket',
371            'Connection': 'Upgrade',
372            'Sec-WebSocket-Key': ws.key(),
373            'Sec-WebSocket-Protocol': 'chat',
374            'Sec-WebSocket-Version': 13,
375        },
376        http_10=True,
377    )
378
379    assert resp['status'] == 400, 'status'
380
381
382def test_asgi_websockets_handshake_uri_invalid():
383    client.load('websockets/mirror')
384
385    resp = client.get(
386        headers={
387            'Host': 'localhost',
388            'Upgrade': 'websocket',
389            'Connection': 'Upgrade',
390            'Sec-WebSocket-Key': ws.key(),
391            'Sec-WebSocket-Protocol': 'chat',
392            'Sec-WebSocket-Version': 13,
393        },
394        url='!',
395    )
396
397    assert resp['status'] == 400, 'status'
398
399
400def test_asgi_websockets_protocol_absent():
401    client.load('websockets/mirror')
402
403    key = ws.key()
404    resp, sock, _ = ws.upgrade(
405        headers={
406            'Host': 'localhost',
407            'Upgrade': 'websocket',
408            'Connection': 'Upgrade',
409            'Sec-WebSocket-Key': key,
410            'Sec-WebSocket-Version': 13,
411        }
412    )
413    sock.close()
414
415    assert resp['status'] == 101, 'status'
416    assert resp['headers']['Upgrade'] == 'websocket', 'upgrade'
417    assert resp['headers']['Connection'] == 'Upgrade', 'connection'
418    assert resp['headers']['Sec-WebSocket-Accept'] == ws.accept(key), 'key'
419
420
421# autobahn-testsuite
422#
423# Some following tests fail because of Unit does not support UTF-8
424# validation for websocket frames.  It should be implemented
425# by application, if necessary.
426
427
428def test_asgi_websockets_1_1_1__1_1_8():
429    client.load('websockets/mirror')
430
431    opcode = ws.OP_TEXT
432
433    _, sock, _ = ws.upgrade()
434
435    def check_length(length, chopsize=None):
436        payload = '*' * length
437
438        ws.frame_write(sock, opcode, payload, chopsize=chopsize)
439
440        frame = ws.frame_read(sock)
441        check_frame(frame, True, opcode, payload)
442
443    check_length(0)  # 1_1_1
444    check_length(125)  # 1_1_2
445    check_length(126)  # 1_1_3
446    check_length(127)  # 1_1_4
447    check_length(128)  # 1_1_5
448    check_length(65535)  # 1_1_6
449    check_length(65536)  # 1_1_7
450    check_length(65536, chopsize=997)  # 1_1_8
451
452    close_connection(sock)
453
454
455def test_asgi_websockets_1_2_1__1_2_8():
456    client.load('websockets/mirror')
457
458    opcode = ws.OP_BINARY
459
460    _, sock, _ = ws.upgrade()
461
462    def check_length(length, chopsize=None):
463        payload = b'\xfe' * length
464
465        ws.frame_write(sock, opcode, payload, chopsize=chopsize)
466        frame = ws.frame_read(sock)
467
468        check_frame(frame, True, opcode, payload)
469
470    check_length(0)  # 1_2_1
471    check_length(125)  # 1_2_2
472    check_length(126)  # 1_2_3
473    check_length(127)  # 1_2_4
474    check_length(128)  # 1_2_5
475    check_length(65535)  # 1_2_6
476    check_length(65536)  # 1_2_7
477    check_length(65536, chopsize=997)  # 1_2_8
478
479    close_connection(sock)
480
481
482def test_asgi_websockets_2_1__2_6():
483    client.load('websockets/mirror')
484
485    op_ping = ws.OP_PING
486    op_pong = ws.OP_PONG
487
488    _, sock, _ = ws.upgrade()
489
490    def check_ping(payload, chopsize=None, decode=True):
491        ws.frame_write(sock, op_ping, payload, chopsize=chopsize)
492        frame = ws.frame_read(sock)
493
494        check_frame(frame, True, op_pong, payload, decode=decode)
495
496    check_ping('')  # 2_1
497    check_ping('Hello, world!')  # 2_2
498    check_ping(b'\x00\xff\xfe\xfd\xfc\xfb\x00\xff', decode=False)  # 2_3
499    check_ping(b'\xfe' * 125, decode=False)  # 2_4
500    check_ping(b'\xfe' * 125, chopsize=1, decode=False)  # 2_6
501
502    close_connection(sock)
503
504    # 2_5
505
506    _, sock, _ = ws.upgrade()
507
508    ws.frame_write(sock, ws.OP_PING, b'\xfe' * 126)
509    check_close(sock, 1002)
510
511
512def test_asgi_websockets_2_7__2_9():
513    client.load('websockets/mirror')
514
515    # 2_7
516
517    _, sock, _ = ws.upgrade()
518
519    ws.frame_write(sock, ws.OP_PONG, '')
520    assert client.recvall(sock, read_timeout=0.1) == b'', '2_7'
521
522    # 2_8
523
524    ws.frame_write(sock, ws.OP_PONG, 'unsolicited pong payload')
525    assert client.recvall(sock, read_timeout=0.1) == b'', '2_8'
526
527    # 2_9
528
529    payload = 'ping payload'
530
531    ws.frame_write(sock, ws.OP_PONG, 'unsolicited pong payload')
532    ws.frame_write(sock, ws.OP_PING, payload)
533
534    frame = ws.frame_read(sock)
535    check_frame(frame, True, ws.OP_PONG, payload)
536
537    close_connection(sock)
538
539
540def test_asgi_websockets_2_10__2_11():
541    client.load('websockets/mirror')
542
543    # 2_10
544
545    _, sock, _ = ws.upgrade()
546
547    for i in range(0, 10):
548        ws.frame_write(sock, ws.OP_PING, f'payload-{i}')
549
550    for i in range(0, 10):
551        frame = ws.frame_read(sock)
552        check_frame(frame, True, ws.OP_PONG, f'payload-{i}')
553
554    # 2_11
555
556    for i in range(0, 10):
557        opcode = ws.OP_PING
558        ws.frame_write(sock, opcode, f'payload-{i}', chopsize=1)
559
560    for i in range(0, 10):
561        frame = ws.frame_read(sock)
562        check_frame(frame, True, ws.OP_PONG, f'payload-{i}')
563
564    close_connection(sock)
565
566
567@pytest.mark.skip('not yet')
568def test_asgi_websockets_3_1__3_7():
569    client.load('websockets/mirror')
570
571    payload = 'Hello, world!'
572
573    # 3_1
574
575    _, sock, _ = ws.upgrade()
576
577    ws.frame_write(sock, ws.OP_TEXT, payload, rsv1=True)
578    check_close(sock, 1002)
579
580    # 3_2
581
582    _, sock, _ = ws.upgrade()
583
584    ws.frame_write(sock, ws.OP_TEXT, payload)
585    ws.frame_write(sock, ws.OP_TEXT, payload, rsv2=True)
586    ws.frame_write(sock, ws.OP_PING, '')
587
588    frame = ws.frame_read(sock)
589    check_frame(frame, True, ws.OP_TEXT, payload)
590
591    check_close(sock, 1002, no_close=True)
592
593    assert client.recvall(sock, read_timeout=0.1) == b'', 'empty 3_2'
594    sock.close()
595
596    # 3_3
597
598    _, sock, _ = ws.upgrade()
599
600    ws.frame_write(sock, ws.OP_TEXT, payload)
601
602    frame = ws.frame_read(sock)
603    check_frame(frame, True, ws.OP_TEXT, payload)
604
605    ws.frame_write(sock, ws.OP_TEXT, payload, rsv1=True, rsv2=True)
606
607    check_close(sock, 1002, no_close=True)
608
609    assert client.recvall(sock, read_timeout=0.1) == b'', 'empty 3_3'
610    sock.close()
611
612    # 3_4
613
614    _, sock, _ = ws.upgrade()
615
616    ws.frame_write(sock, ws.OP_TEXT, payload, chopsize=1)
617    ws.frame_write(sock, ws.OP_TEXT, payload, rsv3=True, chopsize=1)
618    ws.frame_write(sock, ws.OP_PING, '')
619
620    frame = ws.frame_read(sock)
621    check_frame(frame, True, ws.OP_TEXT, payload)
622
623    check_close(sock, 1002, no_close=True)
624
625    assert client.recvall(sock, read_timeout=0.1) == b'', 'empty 3_4'
626    sock.close()
627
628    # 3_5
629
630    _, sock, _ = ws.upgrade()
631
632    ws.frame_write(
633        sock,
634        ws.OP_BINARY,
635        b'\x00\xff\xfe\xfd\xfc\xfb\x00\xff',
636        rsv1=True,
637        rsv3=True,
638    )
639
640    check_close(sock, 1002)
641
642    # 3_6
643
644    _, sock, _ = ws.upgrade()
645
646    ws.frame_write(sock, ws.OP_PING, payload, rsv2=True, rsv3=True)
647
648    check_close(sock, 1002)
649
650    # 3_7
651
652    _, sock, _ = ws.upgrade()
653
654    ws.frame_write(sock, ws.OP_CLOSE, payload, rsv1=True, rsv2=True, rsv3=True)
655
656    check_close(sock, 1002)
657
658
659def test_asgi_websockets_4_1_1__4_2_5():
660    client.load('websockets/mirror')
661
662    payload = 'Hello, world!'
663
664    # 4_1_1
665
666    _, sock, _ = ws.upgrade()
667
668    ws.frame_write(sock, 0x03, '')
669    check_close(sock, 1002)
670
671    # 4_1_2
672
673    _, sock, _ = ws.upgrade()
674
675    ws.frame_write(sock, 0x04, 'reserved opcode payload')
676    check_close(sock, 1002)
677
678    # 4_1_3
679
680    _, sock, _ = ws.upgrade()
681
682    ws.frame_write(sock, ws.OP_TEXT, payload)
683
684    frame = ws.frame_read(sock)
685    check_frame(frame, True, ws.OP_TEXT, payload)
686
687    ws.frame_write(sock, 0x05, '')
688    ws.frame_write(sock, ws.OP_PING, '')
689
690    check_close(sock, 1002)
691
692    # 4_1_4
693
694    _, sock, _ = ws.upgrade()
695
696    ws.frame_write(sock, ws.OP_TEXT, payload)
697
698    frame = ws.frame_read(sock)
699    check_frame(frame, True, ws.OP_TEXT, payload)
700
701    ws.frame_write(sock, 0x06, payload)
702    ws.frame_write(sock, ws.OP_PING, '')
703
704    check_close(sock, 1002)
705
706    # 4_1_5
707
708    _, sock, _ = ws.upgrade()
709
710    ws.frame_write(sock, ws.OP_TEXT, payload, chopsize=1)
711
712    frame = ws.frame_read(sock)
713    check_frame(frame, True, ws.OP_TEXT, payload)
714
715    ws.frame_write(sock, 0x07, payload, chopsize=1)
716    ws.frame_write(sock, ws.OP_PING, '')
717
718    check_close(sock, 1002)
719
720    # 4_2_1
721
722    _, sock, _ = ws.upgrade()
723
724    ws.frame_write(sock, 0x0B, '')
725    check_close(sock, 1002)
726
727    # 4_2_2
728
729    _, sock, _ = ws.upgrade()
730
731    ws.frame_write(sock, 0x0C, 'reserved opcode payload')
732    check_close(sock, 1002)
733
734    # 4_2_3
735
736    _, sock, _ = ws.upgrade()
737
738    ws.frame_write(sock, ws.OP_TEXT, payload)
739
740    frame = ws.frame_read(sock)
741    check_frame(frame, True, ws.OP_TEXT, payload)
742
743    ws.frame_write(sock, 0x0D, '')
744    ws.frame_write(sock, ws.OP_PING, '')
745
746    check_close(sock, 1002)
747
748    # 4_2_4
749
750    _, sock, _ = ws.upgrade()
751
752    ws.frame_write(sock, ws.OP_TEXT, payload)
753
754    frame = ws.frame_read(sock)
755    check_frame(frame, True, ws.OP_TEXT, payload)
756
757    ws.frame_write(sock, 0x0E, payload)
758    ws.frame_write(sock, ws.OP_PING, '')
759
760    check_close(sock, 1002)
761
762    # 4_2_5
763
764    _, sock, _ = ws.upgrade()
765
766    ws.frame_write(sock, ws.OP_TEXT, payload, chopsize=1)
767
768    frame = ws.frame_read(sock)
769    check_frame(frame, True, ws.OP_TEXT, payload)
770
771    ws.frame_write(sock, 0x0F, payload, chopsize=1)
772    ws.frame_write(sock, ws.OP_PING, '')
773
774    check_close(sock, 1002)
775
776
777def test_asgi_websockets_5_1__5_20():
778    client.load('websockets/mirror')
779
780    # 5_1
781
782    _, sock, _ = ws.upgrade()
783
784    ws.frame_write(sock, ws.OP_PING, 'fragment1', fin=False)
785    ws.frame_write(sock, ws.OP_CONT, 'fragment2', fin=True)
786    check_close(sock, 1002)
787
788    # 5_2
789
790    _, sock, _ = ws.upgrade()
791
792    ws.frame_write(sock, ws.OP_PONG, 'fragment1', fin=False)
793    ws.frame_write(sock, ws.OP_CONT, 'fragment2', fin=True)
794    check_close(sock, 1002)
795
796    # 5_3
797
798    _, sock, _ = ws.upgrade()
799
800    ws.frame_write(sock, ws.OP_TEXT, 'fragment1', fin=False)
801    ws.frame_write(sock, ws.OP_CONT, 'fragment2', fin=True)
802
803    frame = ws.frame_read(sock)
804    check_frame(frame, True, ws.OP_TEXT, 'fragment1fragment2')
805
806    # 5_4
807
808    ws.frame_write(sock, ws.OP_TEXT, 'fragment1', fin=False)
809    assert client.recvall(sock, read_timeout=0.1) == b'', '5_4'
810    ws.frame_write(sock, ws.OP_CONT, 'fragment2', fin=True)
811
812    frame = ws.frame_read(sock)
813    check_frame(frame, True, ws.OP_TEXT, 'fragment1fragment2')
814
815    # 5_5
816
817    ws.frame_write(sock, ws.OP_TEXT, 'fragment1', fin=False, chopsize=1)
818    ws.frame_write(sock, ws.OP_CONT, 'fragment2', fin=True, chopsize=1)
819
820    frame = ws.frame_read(sock)
821    check_frame(frame, True, ws.OP_TEXT, 'fragment1fragment2')
822
823    # 5_6
824
825    ping_payload = 'ping payload'
826
827    ws.frame_write(sock, ws.OP_TEXT, 'fragment1', fin=False)
828    ws.frame_write(sock, ws.OP_PING, ping_payload)
829    ws.frame_write(sock, ws.OP_CONT, 'fragment2', fin=True)
830
831    frame = ws.frame_read(sock)
832    check_frame(frame, True, ws.OP_PONG, ping_payload)
833
834    frame = ws.frame_read(sock)
835    check_frame(frame, True, ws.OP_TEXT, 'fragment1fragment2')
836
837    # 5_7
838
839    ping_payload = 'ping payload'
840
841    ws.frame_write(sock, ws.OP_TEXT, 'fragment1', fin=False)
842    assert client.recvall(sock, read_timeout=0.1) == b'', '5_7'
843
844    ws.frame_write(sock, ws.OP_PING, ping_payload)
845
846    frame = ws.frame_read(sock)
847    check_frame(frame, True, ws.OP_PONG, ping_payload)
848
849    ws.frame_write(sock, ws.OP_CONT, 'fragment2', fin=True)
850
851    frame = ws.frame_read(sock)
852    check_frame(frame, True, ws.OP_TEXT, 'fragment1fragment2')
853
854    # 5_8
855
856    ping_payload = 'ping payload'
857
858    ws.frame_write(sock, ws.OP_TEXT, 'fragment1', fin=False, chopsize=1)
859    ws.frame_write(sock, ws.OP_PING, ping_payload, chopsize=1)
860    ws.frame_write(sock, ws.OP_CONT, 'fragment2', fin=True, chopsize=1)
861
862    frame = ws.frame_read(sock)
863    check_frame(frame, True, ws.OP_PONG, ping_payload)
864
865    frame = ws.frame_read(sock)
866    check_frame(frame, True, ws.OP_TEXT, 'fragment1fragment2')
867
868    # 5_9
869
870    ws.frame_write(sock, ws.OP_CONT, 'non-continuation payload', fin=True)
871    ws.frame_write(sock, ws.OP_TEXT, 'Hello, world!', fin=True)
872    check_close(sock, 1002)
873
874    # 5_10
875
876    _, sock, _ = ws.upgrade()
877
878    ws.frame_write(sock, ws.OP_CONT, 'non-continuation payload', fin=True)
879    ws.frame_write(sock, ws.OP_TEXT, 'Hello, world!', fin=True)
880    check_close(sock, 1002)
881
882    # 5_11
883
884    _, sock, _ = ws.upgrade()
885
886    ws.frame_write(
887        sock,
888        ws.OP_CONT,
889        'non-continuation payload',
890        fin=True,
891        chopsize=1,
892    )
893    ws.frame_write(sock, ws.OP_TEXT, 'Hello, world!', fin=True, chopsize=1)
894    check_close(sock, 1002)
895
896    # 5_12
897
898    _, sock, _ = ws.upgrade()
899
900    ws.frame_write(sock, ws.OP_CONT, 'non-continuation payload', fin=False)
901    ws.frame_write(sock, ws.OP_TEXT, 'Hello, world!', fin=True)
902    check_close(sock, 1002)
903
904    # 5_13
905
906    _, sock, _ = ws.upgrade()
907
908    ws.frame_write(sock, ws.OP_CONT, 'non-continuation payload', fin=False)
909    ws.frame_write(sock, ws.OP_TEXT, 'Hello, world!', fin=True)
910    check_close(sock, 1002)
911
912    # 5_14
913
914    _, sock, _ = ws.upgrade()
915
916    ws.frame_write(
917        sock,
918        ws.OP_CONT,
919        'non-continuation payload',
920        fin=False,
921        chopsize=1,
922    )
923    ws.frame_write(sock, ws.OP_TEXT, 'Hello, world!', fin=True, chopsize=1)
924    check_close(sock, 1002)
925
926    # 5_15
927
928    _, sock, _ = ws.upgrade()
929
930    ws.frame_write(sock, ws.OP_TEXT, 'fragment1', fin=False)
931    ws.frame_write(sock, ws.OP_CONT, 'fragment2', fin=True)
932    ws.frame_write(sock, ws.OP_CONT, 'fragment3', fin=False)
933    ws.frame_write(sock, ws.OP_TEXT, 'fragment4', fin=True)
934
935    frame = ws.frame_read(sock)
936
937    if frame['opcode'] == ws.OP_TEXT:
938        check_frame(frame, True, ws.OP_TEXT, 'fragment1fragment2')
939        frame = None
940
941    check_close(sock, 1002, frame=frame)
942
943    # 5_16
944
945    _, sock, _ = ws.upgrade()
946
947    for _ in range(0, 2):
948        ws.frame_write(sock, ws.OP_CONT, 'fragment1', fin=False)
949        ws.frame_write(sock, ws.OP_TEXT, 'fragment2', fin=False)
950        ws.frame_write(sock, ws.OP_CONT, 'fragment3', fin=True)
951    check_close(sock, 1002)
952
953    # 5_17
954
955    _, sock, _ = ws.upgrade()
956
957    for _ in range(0, 2):
958        ws.frame_write(sock, ws.OP_CONT, 'fragment1', fin=True)
959        ws.frame_write(sock, ws.OP_TEXT, 'fragment2', fin=False)
960        ws.frame_write(sock, ws.OP_CONT, 'fragment3', fin=True)
961    check_close(sock, 1002)
962
963    # 5_18
964
965    _, sock, _ = ws.upgrade()
966
967    ws.frame_write(sock, ws.OP_TEXT, 'fragment1', fin=False)
968    ws.frame_write(sock, ws.OP_TEXT, 'fragment2')
969    check_close(sock, 1002)
970
971    # 5_19
972
973    _, sock, _ = ws.upgrade()
974
975    ws.frame_write(sock, ws.OP_TEXT, 'fragment1', fin=False)
976    ws.frame_write(sock, ws.OP_CONT, 'fragment2', fin=False)
977    ws.frame_write(sock, ws.OP_PING, 'pongme 1!')
978
979    time.sleep(1)
980
981    ws.frame_write(sock, ws.OP_CONT, 'fragment3', fin=False)
982    ws.frame_write(sock, ws.OP_CONT, 'fragment4', fin=False)
983    ws.frame_write(sock, ws.OP_PING, 'pongme 2!')
984    ws.frame_write(sock, ws.OP_CONT, 'fragment5')
985
986    frame = ws.frame_read(sock)
987    check_frame(frame, True, ws.OP_PONG, 'pongme 1!')
988
989    frame = ws.frame_read(sock)
990    check_frame(frame, True, ws.OP_PONG, 'pongme 2!')
991
992    check_frame(
993        ws.frame_read(sock),
994        True,
995        ws.OP_TEXT,
996        'fragment1fragment2fragment3fragment4fragment5',
997    )
998
999    # 5_20
1000
1001    ws.frame_write(sock, ws.OP_TEXT, 'fragment1', fin=False)
1002    ws.frame_write(sock, ws.OP_CONT, 'fragment2', fin=False)
1003    ws.frame_write(sock, ws.OP_PING, 'pongme 1!')
1004
1005    frame = ws.frame_read(sock)
1006    check_frame(frame, True, ws.OP_PONG, 'pongme 1!')
1007
1008    time.sleep(1)
1009
1010    ws.frame_write(sock, ws.OP_CONT, 'fragment3', fin=False)
1011    ws.frame_write(sock, ws.OP_CONT, 'fragment4', fin=False)
1012    ws.frame_write(sock, ws.OP_PING, 'pongme 2!')
1013
1014    frame = ws.frame_read(sock)
1015    check_frame(frame, True, ws.OP_PONG, 'pongme 2!')
1016
1017    assert client.recvall(sock, read_timeout=0.1) == b'', '5_20'
1018    ws.frame_write(sock, ws.OP_CONT, 'fragment5')
1019
1020    check_frame(
1021        ws.frame_read(sock),
1022        True,
1023        ws.OP_TEXT,
1024        'fragment1fragment2fragment3fragment4fragment5',
1025    )
1026
1027    close_connection(sock)
1028
1029
1030def test_asgi_websockets_6_1_1__6_4_4():
1031    client.load('websockets/mirror')
1032
1033    # 6_1_1
1034
1035    _, sock, _ = ws.upgrade()
1036
1037    ws.frame_write(sock, ws.OP_TEXT, '')
1038    frame = ws.frame_read(sock)
1039    check_frame(frame, True, ws.OP_TEXT, '')
1040
1041    # 6_1_2
1042
1043    ws.frame_write(sock, ws.OP_TEXT, '', fin=False)
1044    ws.frame_write(sock, ws.OP_CONT, '', fin=False)
1045    ws.frame_write(sock, ws.OP_CONT, '')
1046
1047    frame = ws.frame_read(sock)
1048    check_frame(frame, True, ws.OP_TEXT, '')
1049
1050    # 6_1_3
1051
1052    payload = 'middle frame payload'
1053
1054    ws.frame_write(sock, ws.OP_TEXT, '', fin=False)
1055    ws.frame_write(sock, ws.OP_CONT, payload, fin=False)
1056    ws.frame_write(sock, ws.OP_CONT, '')
1057
1058    frame = ws.frame_read(sock)
1059    check_frame(frame, True, ws.OP_TEXT, payload)
1060
1061    # 6_2_1
1062
1063    payload = 'Hello-µ@ßöäüàá-UTF-8!!'
1064
1065    ws.frame_write(sock, ws.OP_TEXT, payload)
1066
1067    frame = ws.frame_read(sock)
1068    check_frame(frame, True, ws.OP_TEXT, payload)
1069
1070    # 6_2_2
1071
1072    ws.frame_write(sock, ws.OP_TEXT, payload[:12], fin=False)
1073    ws.frame_write(sock, ws.OP_CONT, payload[12:])
1074
1075    frame = ws.frame_read(sock)
1076    check_frame(frame, True, ws.OP_TEXT, payload)
1077
1078    # 6_2_3
1079
1080    ws.message(sock, ws.OP_TEXT, payload, fragmention_size=1)
1081
1082    frame = ws.frame_read(sock)
1083    check_frame(frame, True, ws.OP_TEXT, payload)
1084
1085    # 6_2_4
1086
1087    payload = '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5'
1088
1089    ws.message(sock, ws.OP_TEXT, payload, fragmention_size=1)
1090
1091    frame = ws.frame_read(sock)
1092    check_frame(frame, True, ws.OP_TEXT, payload)
1093
1094    close_connection(sock)
1095
1096
1097#        Unit does not support UTF-8 validation
1098#
1099#        # 6_3_1 FAIL
1100#
1101#        payload_1 = '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5'
1102#        payload_2 = '\xed\xa0\x80'
1103#        payload_3 = '\x65\x64\x69\x74\x65\x64'
1104#
1105#        payload = payload_1 + payload_2 + payload_3
1106#
1107#        ws.message(sock, ws.OP_TEXT, payload)
1108#        check_close(sock, 1007)
1109#
1110#        # 6_3_2 FAIL
1111#
1112#        _, sock, _ = ws.upgrade()
1113#
1114#        ws.message(sock, ws.OP_TEXT, payload, fragmention_size=1)
1115#        check_close(sock, 1007)
1116#
1117#        # 6_4_1 ... 6_4_4 FAIL
1118
1119
1120def test_asgi_websockets_7_1_1__7_5_1():
1121    client.load('websockets/mirror')
1122
1123    # 7_1_1
1124
1125    _, sock, _ = ws.upgrade()
1126
1127    payload = "Hello World!"
1128
1129    ws.frame_write(sock, ws.OP_TEXT, payload)
1130
1131    frame = ws.frame_read(sock)
1132    check_frame(frame, True, ws.OP_TEXT, payload)
1133
1134    close_connection(sock)
1135
1136    # 7_1_2
1137
1138    _, sock, _ = ws.upgrade()
1139
1140    ws.frame_write(sock, ws.OP_CLOSE, ws.serialize_close())
1141    ws.frame_write(sock, ws.OP_CLOSE, ws.serialize_close())
1142
1143    check_close(sock)
1144
1145    # 7_1_3
1146
1147    _, sock, _ = ws.upgrade()
1148
1149    ws.frame_write(sock, ws.OP_CLOSE, ws.serialize_close())
1150    check_close(sock, no_close=True)
1151
1152    ws.frame_write(sock, ws.OP_PING, '')
1153    assert client.recvall(sock, read_timeout=0.1) == b'', 'empty soc'
1154
1155    sock.close()
1156
1157    # 7_1_4
1158
1159    _, sock, _ = ws.upgrade()
1160
1161    ws.frame_write(sock, ws.OP_CLOSE, ws.serialize_close())
1162    check_close(sock, no_close=True)
1163
1164    ws.frame_write(sock, ws.OP_TEXT, payload)
1165    assert client.recvall(sock, read_timeout=0.1) == b'', 'empty soc'
1166
1167    sock.close()
1168
1169    # 7_1_5
1170
1171    _, sock, _ = ws.upgrade()
1172
1173    ws.frame_write(sock, ws.OP_TEXT, 'fragment1', fin=False)
1174    ws.frame_write(sock, ws.OP_CLOSE, ws.serialize_close())
1175    check_close(sock, no_close=True)
1176
1177    ws.frame_write(sock, ws.OP_CONT, 'fragment2')
1178    assert client.recvall(sock, read_timeout=0.1) == b'', 'empty soc'
1179
1180    sock.close()
1181
1182    # 7_1_6
1183
1184    _, sock, _ = ws.upgrade()
1185
1186    ws.frame_write(sock, ws.OP_TEXT, 'BAsd7&jh23' * 26 * 2**10)
1187    ws.frame_write(sock, ws.OP_TEXT, payload)
1188    ws.frame_write(sock, ws.OP_CLOSE, ws.serialize_close())
1189
1190    client.recvall(sock, read_timeout=1)
1191
1192    ws.frame_write(sock, ws.OP_PING, '')
1193    assert client.recvall(sock, read_timeout=0.1) == b'', 'empty soc'
1194
1195    sock.close()
1196
1197    # 7_3_1
1198
1199    _, sock, _ = ws.upgrade()
1200
1201    ws.frame_write(sock, ws.OP_CLOSE, '')
1202    check_close(sock)
1203
1204    # 7_3_2
1205
1206    _, sock, _ = ws.upgrade()
1207
1208    ws.frame_write(sock, ws.OP_CLOSE, 'a')
1209    check_close(sock, 1002)
1210
1211    # 7_3_3
1212
1213    _, sock, _ = ws.upgrade()
1214
1215    ws.frame_write(sock, ws.OP_CLOSE, ws.serialize_close())
1216    check_close(sock)
1217
1218    # 7_3_4
1219
1220    _, sock, _ = ws.upgrade()
1221
1222    payload = ws.serialize_close(reason='Hello World!')
1223
1224    ws.frame_write(sock, ws.OP_CLOSE, payload)
1225    check_close(sock)
1226
1227    # 7_3_5
1228
1229    _, sock, _ = ws.upgrade()
1230
1231    payload = ws.serialize_close(reason='*' * 123)
1232
1233    ws.frame_write(sock, ws.OP_CLOSE, payload)
1234    check_close(sock)
1235
1236    # 7_3_6
1237
1238    _, sock, _ = ws.upgrade()
1239
1240    payload = ws.serialize_close(reason='*' * 124)
1241
1242    ws.frame_write(sock, ws.OP_CLOSE, payload)
1243    check_close(sock, 1002)
1244
1245
1246#        # 7_5_1 FAIL Unit does not support UTF-8 validation
1247#
1248#        _, sock, _ = ws.upgrade()
1249#
1250#        payload = ws.serialize_close(reason = '\xce\xba\xe1\xbd\xb9\xcf' \
1251#            '\x83\xce\xbc\xce\xb5\xed\xa0\x80\x65\x64\x69\x74\x65\x64')
1252#
1253#        ws.frame_write(sock, ws.OP_CLOSE, payload)
1254#        check_close(sock, 1007)
1255
1256
1257def test_asgi_websockets_7_7_X__7_9_X():
1258    client.load('websockets/mirror')
1259
1260    valid_codes = [
1261        1000,
1262        1001,
1263        1002,
1264        1003,
1265        1007,
1266        1008,
1267        1009,
1268        1010,
1269        1011,
1270        3000,
1271        3999,
1272        4000,
1273        4999,
1274    ]
1275
1276    invalid_codes = [0, 999, 1004, 1005, 1006, 1016, 1100, 2000, 2999]
1277
1278    for code in valid_codes:
1279        _, sock, _ = ws.upgrade()
1280
1281        payload = ws.serialize_close(code=code)
1282
1283        ws.frame_write(sock, ws.OP_CLOSE, payload)
1284        check_close(sock)
1285
1286    for code in invalid_codes:
1287        _, sock, _ = ws.upgrade()
1288
1289        payload = ws.serialize_close(code=code)
1290
1291        ws.frame_write(sock, ws.OP_CLOSE, payload)
1292        check_close(sock, 1002)
1293
1294
1295def test_asgi_websockets_7_13_1__7_13_2():
1296    client.load('websockets/mirror')
1297
1298    # 7_13_1
1299
1300    _, sock, _ = ws.upgrade()
1301
1302    payload = ws.serialize_close(code=5000)
1303
1304    ws.frame_write(sock, ws.OP_CLOSE, payload)
1305    check_close(sock, 1002)
1306
1307    # 7_13_2
1308
1309    _, sock, _ = ws.upgrade()
1310
1311    payload = struct.pack('!I', 65536) + ''.encode('utf-8')
1312
1313    ws.frame_write(sock, ws.OP_CLOSE, payload)
1314    check_close(sock, 1002)
1315
1316
1317def test_asgi_websockets_9_1_1__9_6_6(is_unsafe, system):
1318    if not is_unsafe:
1319        pytest.skip('unsafe, long run')
1320
1321    client.load('websockets/mirror')
1322
1323    assert 'success' in client.conf(
1324        {
1325            'http': {
1326                'websocket': {
1327                    'max_frame_size': 33554432,
1328                    'keepalive_interval': 0,
1329                }
1330            }
1331        },
1332        'settings',
1333    ), 'increase max_frame_size and keepalive_interval'
1334
1335    _, sock, _ = ws.upgrade()
1336
1337    op_text = ws.OP_TEXT
1338    op_binary = ws.OP_BINARY
1339
1340    def check_payload(opcode, length, chopsize=None):
1341        if opcode == ws.OP_TEXT:
1342            payload = '*' * length
1343        else:
1344            payload = b'*' * length
1345
1346        ws.frame_write(sock, opcode, payload, chopsize=chopsize)
1347        frame = ws.frame_read(sock, read_timeout=5)
1348        check_frame(frame, True, opcode, payload)
1349
1350    def check_message(opcode, f_size):
1351        if opcode == ws.OP_TEXT:
1352            payload = '*' * 4 * 2**20
1353        else:
1354            payload = b'*' * 4 * 2**20
1355
1356        ws.message(sock, opcode, payload, fragmention_size=f_size)
1357        frame = ws.frame_read(sock, read_timeout=5)
1358        check_frame(frame, True, opcode, payload)
1359
1360    check_payload(op_text, 64 * 2**10)  # 9_1_1
1361    check_payload(op_text, 256 * 2**10)  # 9_1_2
1362    check_payload(op_text, 2**20)  # 9_1_3
1363    check_payload(op_text, 4 * 2**20)  # 9_1_4
1364    check_payload(op_text, 8 * 2**20)  # 9_1_5
1365    check_payload(op_text, 16 * 2**20)  # 9_1_6
1366
1367    check_payload(op_binary, 64 * 2**10)  # 9_2_1
1368    check_payload(op_binary, 256 * 2**10)  # 9_2_2
1369    check_payload(op_binary, 2**20)  # 9_2_3
1370    check_payload(op_binary, 4 * 2**20)  # 9_2_4
1371    check_payload(op_binary, 8 * 2**20)  # 9_2_5
1372    check_payload(op_binary, 16 * 2**20)  # 9_2_6
1373
1374    if system not in ['Darwin', 'FreeBSD']:
1375        check_message(op_text, 64)  # 9_3_1
1376        check_message(op_text, 256)  # 9_3_2
1377        check_message(op_text, 2**10)  # 9_3_3
1378        check_message(op_text, 4 * 2**10)  # 9_3_4
1379        check_message(op_text, 16 * 2**10)  # 9_3_5
1380        check_message(op_text, 64 * 2**10)  # 9_3_6
1381        check_message(op_text, 256 * 2**10)  # 9_3_7
1382        check_message(op_text, 2**20)  # 9_3_8
1383        check_message(op_text, 4 * 2**20)  # 9_3_9
1384
1385        check_message(op_binary, 64)  # 9_4_1
1386        check_message(op_binary, 256)  # 9_4_2
1387        check_message(op_binary, 2**10)  # 9_4_3
1388        check_message(op_binary, 4 * 2**10)  # 9_4_4
1389        check_message(op_binary, 16 * 2**10)  # 9_4_5
1390        check_message(op_binary, 64 * 2**10)  # 9_4_6
1391        check_message(op_binary, 256 * 2**10)  # 9_4_7
1392        check_message(op_binary, 2**20)  # 9_4_8
1393        check_message(op_binary, 4 * 2**20)  # 9_4_9
1394
1395    check_payload(op_text, 2**20, chopsize=64)  # 9_5_1
1396    check_payload(op_text, 2**20, chopsize=128)  # 9_5_2
1397    check_payload(op_text, 2**20, chopsize=256)  # 9_5_3
1398    check_payload(op_text, 2**20, chopsize=512)  # 9_5_4
1399    check_payload(op_text, 2**20, chopsize=1024)  # 9_5_5
1400    check_payload(op_text, 2**20, chopsize=2048)  # 9_5_6
1401
1402    check_payload(op_binary, 2**20, chopsize=64)  # 9_6_1
1403    check_payload(op_binary, 2**20, chopsize=128)  # 9_6_2
1404    check_payload(op_binary, 2**20, chopsize=256)  # 9_6_3
1405    check_payload(op_binary, 2**20, chopsize=512)  # 9_6_4
1406    check_payload(op_binary, 2**20, chopsize=1024)  # 9_6_5
1407    check_payload(op_binary, 2**20, chopsize=2048)  # 9_6_6
1408
1409    close_connection(sock)
1410
1411
1412def test_asgi_websockets_10_1_1():
1413    client.load('websockets/mirror')
1414
1415    _, sock, _ = ws.upgrade()
1416
1417    payload = '*' * 65536
1418
1419    ws.message(sock, ws.OP_TEXT, payload, fragmention_size=1300)
1420
1421    frame = ws.frame_read(sock)
1422    check_frame(frame, True, ws.OP_TEXT, payload)
1423
1424    close_connection(sock)
1425
1426
1427# settings
1428
1429
1430def test_asgi_websockets_max_frame_size():
1431    client.load('websockets/mirror')
1432
1433    assert 'success' in client.conf(
1434        {'http': {'websocket': {'max_frame_size': 100}}}, 'settings'
1435    ), 'configure max_frame_size'
1436
1437    _, sock, _ = ws.upgrade()
1438
1439    payload = '*' * 94
1440    opcode = ws.OP_TEXT
1441
1442    ws.frame_write(sock, opcode, payload)  # frame length is 100
1443
1444    frame = ws.frame_read(sock)
1445    check_frame(frame, True, opcode, payload)
1446
1447    payload = '*' * 95
1448
1449    ws.frame_write(sock, opcode, payload)  # frame length is 101
1450    check_close(sock, 1009)  # 1009 - CLOSE_TOO_LARGE
1451
1452
1453def test_asgi_websockets_read_timeout():
1454    client.load('websockets/mirror')
1455
1456    assert 'success' in client.conf(
1457        {'http': {'websocket': {'read_timeout': 5}}}, 'settings'
1458    ), 'configure read_timeout'
1459
1460    _, sock, _ = ws.upgrade()
1461
1462    frame = ws.frame_to_send(ws.OP_TEXT, 'blah')
1463    sock.sendall(frame[:2])
1464
1465    time.sleep(2)
1466
1467    check_close(sock, 1001)  # 1001 - CLOSE_GOING_AWAY
1468
1469
1470def test_asgi_websockets_keepalive_interval():
1471    client.load('websockets/mirror')
1472
1473    assert 'success' in client.conf(
1474        {'http': {'websocket': {'keepalive_interval': 5}}}, 'settings'
1475    ), 'configure keepalive_interval'
1476
1477    _, sock, _ = ws.upgrade()
1478
1479    frame = ws.frame_to_send(ws.OP_TEXT, 'blah')
1480    sock.sendall(frame[:2])
1481
1482    time.sleep(2)
1483
1484    frame = ws.frame_read(sock)
1485    check_frame(frame, True, ws.OP_PING, '')  # PING frame
1486
1487    sock.close()
1488
1489
1490def test_asgi_websockets_client_locks_app():
1491    client.load('websockets/mirror')
1492
1493    message = 'blah'
1494
1495    _, sock, _ = ws.upgrade()
1496
1497    assert 'success' in client.conf({}), 'remove app'
1498
1499    ws.frame_write(sock, ws.OP_TEXT, message)
1500
1501    frame = ws.frame_read(sock)
1502
1503    assert message == frame['data'].decode('utf-8'), 'client'
1504
1505    sock.close()
1506