xref: /unit/test/test_proxy_chunked.py (revision 2616:ab2896c980ab)
1import re
2import select
3import socket
4import time
5
6import pytest
7
8from conftest import run_process
9from unit.applications.lang.python import ApplicationPython
10from unit.utils import waitforsocket
11
12prerequisites = {'modules': {'python': 'any'}}
13
14client = ApplicationPython()
15SERVER_PORT = 7999
16
17
18@pytest.fixture(autouse=True)
19def setup_method_fixture():
20    run_process(run_server, SERVER_PORT)
21    waitforsocket(SERVER_PORT)
22
23    assert 'success' in client.conf(
24        {
25            "listeners": {
26                "*:8080": {"pass": "routes"},
27            },
28            "routes": [
29                {"action": {"proxy": f'http://127.0.0.1:{SERVER_PORT}'}}
30            ],
31        }
32    ), 'proxy initial configuration'
33
34
35def run_server(server_port):
36    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
37    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
38    sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
39
40    server_address = ('127.0.0.1', server_port)
41    sock.bind(server_address)
42    sock.listen(10)
43
44    def recvall(sock):
45        buff_size = 4096 * 4096
46        data = b''
47        while True:
48            rlist = select.select([sock], [], [], 0.1)
49
50            if not rlist[0]:
51                break
52
53            part = sock.recv(buff_size)
54            data += part
55
56            if not part:
57                break
58
59        return data
60
61    while True:
62        connection, _ = sock.accept()
63
64        req = """HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked"""
65
66        data = recvall(connection).decode()
67
68        m = re.search('\x0d\x0a\x0d\x0a(.*)', data, re.M | re.S)
69        if m is not None:
70            body = m.group(1)
71
72            for line in re.split('\r\n', body):
73                add = ''
74                m1 = re.search(r'(.*)\sX\s(\d+)', line)
75
76                if m1 is not None:
77                    add = m1.group(1) * int(m1.group(2))
78                else:
79                    add = line
80
81                req = f'{req}{add}\r\n'
82
83        for chunk in re.split(r'([@#])', req):
84            if chunk in ('@', '#'):
85                if chunk == '#':
86                    time.sleep(0.1)
87                continue
88
89            connection.sendall(chunk.encode())
90
91        connection.close()
92
93
94def chunks(chunks_lst):
95    body = '\r\n\r\n'
96
97    for l, c in chunks_lst:
98        body = f'{body}{l}\r\n{c}\r\n'
99
100    return f'{body}0\r\n\r\n'
101
102
103def get_http10(*args, **kwargs):
104    return client.get(*args, http_10=True, **kwargs)
105
106
107def test_proxy_chunked():
108    for _ in range(10):
109        assert get_http10(body='\r\n\r\n0\r\n\r\n')['status'] == 200
110
111
112def test_proxy_chunked_body():
113    part = '0123456789abcdef'
114
115    assert (
116        get_http10(body=chunks([('1000', f'{part} X 256')]))['body']
117        == part * 256
118    )
119    assert (
120        get_http10(body=chunks([('100000', f'{part} X 65536')]))['body']
121        == part * 65536
122    )
123    assert (
124        get_http10(
125            body=chunks([('1000000', f'{part} X 1048576')]),
126            read_buffer_size=4096 * 4096,
127        )['body']
128        == part * 1048576
129    )
130
131    assert (
132        get_http10(
133            body=chunks([('1000', f'{part} X 256'), ('1000', f'{part} X 256')])
134        )['body']
135        == part * 256 * 2
136    )
137    assert (
138        get_http10(
139            body=chunks(
140                [
141                    ('100000', f'{part} X 65536'),
142                    ('100000', f'{part} X 65536'),
143                ]
144            )
145        )['body']
146        == part * 65536 * 2
147    )
148    assert (
149        get_http10(
150            body=chunks(
151                [
152                    ('1000000', f'{part} X 1048576'),
153                    ('1000000', f'{part} X 1048576'),
154                ]
155            ),
156            read_buffer_size=4096 * 4096,
157        )['body']
158        == part * 1048576 * 2
159    )
160
161
162def test_proxy_chunked_fragmented():
163    part = '0123456789abcdef'
164
165    assert (
166        get_http10(
167            body=chunks([('1', hex(i % 16)[2:]) for i in range(4096)]),
168        )['body']
169        == part * 256
170    )
171
172
173def test_proxy_chunked_send():
174    assert get_http10(body='\r\n\r\n@0@\r\n\r\n')['status'] == 200
175    assert (
176        get_http10(body='\r@\n\r\n2\r@\na@b\r\n2\r\ncd@\r\n0\r@\n\r\n')['body']
177        == 'abcd'
178    )
179    assert (
180        get_http10(body='\r\n\r\n2\r#\na#b\r\n##2\r\n#cd\r\n0\r\n#\r#\n')[
181            'body'
182        ]
183        == 'abcd'
184    )
185
186
187def test_proxy_chunked_invalid():
188    def check_invalid(body):
189        assert get_http10(body=body)['status'] != 200
190
191    check_invalid('\r\n\r0')
192    check_invalid('\r\n\r\n\r0')
193    check_invalid('\r\n\r\n\r\n0')
194    check_invalid('\r\nContent-Length: 5\r\n\r\n0\r\n\r\n')
195    check_invalid('\r\n\r\n1\r\nXX\r\n0\r\n\r\n')
196    check_invalid('\r\n\r\n2\r\nX\r\n0\r\n\r\n')
197    check_invalid('\r\n\r\nH\r\nXX\r\n0\r\n\r\n')
198    check_invalid('\r\n\r\n0\r\nX')
199
200    resp = get_http10(body='\r\n\r\n65#\r\nA X 100')
201    assert resp['status'] == 200, 'incomplete chunk status'
202    assert resp['body'][-5:] != '0\r\n\r\n', 'incomplete chunk'
203
204    resp = get_http10(body='\r\n\r\n64#\r\nA X 100')
205    assert resp['status'] == 200, 'no zero chunk status'
206    assert resp['body'][-5:] != '0\r\n\r\n', 'no zero chunk'
207
208    assert get_http10(body='\r\n\r\n80000000\r\nA X 100')['status'] == 200
209    assert (
210        get_http10(body='\r\n\r\n10000000000000000\r\nA X 100')['status'] == 502
211    )
212    assert (
213        len(
214            get_http10(
215                body='\r\n\r\n1000000\r\nA X 1048576\r\n1000000\r\nA X 100',
216                read_buffer_size=4096 * 4096,
217            )['body']
218        )
219        >= 1048576
220    )
221    assert (
222        len(
223            get_http10(
224                body='\r\n\r\n1000000\r\nA X 1048576\r\nXXX\r\nA X 100',
225                read_buffer_size=4096 * 4096,
226            )['body']
227        )
228        >= 1048576
229    )
230