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