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