Deleted Added
1import re
2import socket
3import time
4import unittest
5
6from unit.applications.lang.python import TestApplicationPython
7
8
9class TestProxy(TestApplicationPython):
10 prerequisites = {'modules': {'python': 'any'}}
11
12 SERVER_PORT = 7999
13
14 @staticmethod

--- 22 unchanged lines hidden (view full) ---

37
38 while True:
39 connection, client_address = sock.accept()
40
41 data = recvall(connection).decode()
42
43 to_send = req
44
45 m = re.search('X-Len: (\d+)', data)
46 if m:
47 to_send += b'X' * int(m.group(1))
48
49 connection.sendall(to_send)
50
51 connection.close()
52
53 def get_http10(self, *args, **kwargs):
54 return self.get(*args, http_10=True, **kwargs)
55
56 def post_http10(self, *args, **kwargs):
57 return self.post(*args, http_10=True, **kwargs)
58
59 def setUp(self):
60 super().setUp()
61
62 self.run_process(self.run_server, self.SERVER_PORT)
63 self.waitforsocket(self.SERVER_PORT)
64
65 self.assertIn(
66 'success',
67 self.conf(
68 {
69 "listeners": {
70 "*:7080": {"pass": "routes"},
71 "*:7081": {"pass": "applications/mirror"},
72 },
73 "routes": [{"action": {"proxy": "http://127.0.0.1:7081"}}],
74 "applications": {
75 "mirror": {
76 "type": "python",
77 "processes": {"spare": 0},
78 "path": self.current_dir + "/python/mirror",
79 "working_directory": self.current_dir
80 + "/python/mirror",
81 "module": "wsgi",
82 },
83 "custom_header": {
84 "type": "python",
85 "processes": {"spare": 0},
86 "path": self.current_dir + "/python/custom_header",
87 "working_directory": self.current_dir
88 + "/python/custom_header",
89 "module": "wsgi",
90 },
91 "delayed": {
92 "type": "python",
93 "processes": {"spare": 0},
94 "path": self.current_dir + "/python/delayed",
95 "working_directory": self.current_dir
96 + "/python/delayed",
97 "module": "wsgi",
98 },
99 },
100 }
101 ),
102 'proxy initial configuration',
103 )
104
105 def test_proxy_http10(self):
106 for _ in range(10):
107 self.assertEqual(self.get_http10()['status'], 200, 'status')
108
109 def test_proxy_chain(self):
110 self.assertIn(
111 'success',
112 self.conf(
113 {
114 "listeners": {
115 "*:7080": {"pass": "routes/first"},
116 "*:7081": {"pass": "routes/second"},
117 "*:7082": {"pass": "routes/third"},
118 "*:7083": {"pass": "routes/fourth"},
119 "*:7084": {"pass": "routes/fifth"},
120 "*:7085": {"pass": "applications/mirror"},
121 },
122 "routes": {
123 "first": [
124 {"action": {"proxy": "http://127.0.0.1:7081"}}
125 ],
126 "second": [
127 {"action": {"proxy": "http://127.0.0.1:7082"}}
128 ],
129 "third": [
130 {"action": {"proxy": "http://127.0.0.1:7083"}}
131 ],
132 "fourth": [
133 {"action": {"proxy": "http://127.0.0.1:7084"}}
134 ],
135 "fifth": [
136 {"action": {"proxy": "http://127.0.0.1:7085"}}
137 ],
138 },
139 "applications": {
140 "mirror": {
141 "type": "python",
142 "processes": {"spare": 0},
143 "path": self.current_dir + "/python/mirror",
144 "working_directory": self.current_dir
145 + "/python/mirror",
146 "module": "wsgi",
147 }
148 },
149 }
150 ),
151 'proxy chain configuration',
152 )
153
154 self.assertEqual(self.get_http10()['status'], 200, 'status')
155
156 def test_proxy_body(self):
157 payload = '0123456789'
158 for _ in range(10):
159 resp = self.post_http10(body=payload)
160
161 self.assertEqual(resp['status'], 200, 'status')
162 self.assertEqual(resp['body'], payload, 'body')
163
164 payload = 'X' * 4096
165 for _ in range(10):
166 resp = self.post_http10(body=payload)
167
168 self.assertEqual(resp['status'], 200, 'status')
169 self.assertEqual(resp['body'], payload, 'body')
170
171 payload = 'X' * 4097
172 for _ in range(10):
173 resp = self.post_http10(body=payload)
174
175 self.assertEqual(resp['status'], 200, 'status')
176 self.assertEqual(resp['body'], payload, 'body')
177
178 payload = 'X' * 4096 * 256
179 for _ in range(10):
180 resp = self.post_http10(body=payload, read_buffer_size=4096 * 128)
181
182 self.assertEqual(resp['status'], 200, 'status')
183 self.assertEqual(resp['body'], payload, 'body')
184
185 payload = 'X' * 4096 * 257
186 for _ in range(10):
187 resp = self.post_http10(body=payload, read_buffer_size=4096 * 128)
188
189 self.assertEqual(resp['status'], 200, 'status')
190 self.assertEqual(resp['body'], payload, 'body')
191
192 self.conf({'http': {'max_body_size': 32 * 1024 * 1024}}, 'settings')
193
194 payload = '0123456789abcdef' * 32 * 64 * 1024
195 resp = self.post_http10(body=payload, read_buffer_size=1024 * 1024)
196 self.assertEqual(resp['status'], 200, 'status')
197 self.assertEqual(resp['body'], payload, 'body')
198
199 def test_proxy_parallel(self):
200 payload = 'X' * 4096 * 257
201 buff_size = 4096 * 258
202
203 socks = []
204 for i in range(10):
205 _, sock = self.post_http10(

--- 5 unchanged lines hidden (view full) ---

211 socks.append(sock)
212
213 for i in range(10):
214 resp = self.recvall(socks[i], buff_size=buff_size).decode()
215 socks[i].close()
216
217 resp = self._resp_to_dict(resp)
218
219 self.assertEqual(resp['status'], 200, 'status')
220 self.assertEqual(resp['body'], payload + str(i), 'body')
221
222 def test_proxy_header(self):
223 self.assertIn(
224 'success',
225 self.conf(
226 {"pass": "applications/custom_header"}, 'listeners/*:7081'
227 ),
228 'custom_header configure',
229 )
230
231 header_value = 'blah'
232 self.assertEqual(
233 self.get_http10(
234 headers={'Host': 'localhost', 'Custom-Header': header_value}
235 )['headers']['Custom-Header'],
236 header_value,
237 'custom header',
238 )
239
240 header_value = '(),/:;<=>?@[\]{}\t !#$%&\'*+-.^_`|~'
241 self.assertEqual(
242 self.get_http10(
243 headers={'Host': 'localhost', 'Custom-Header': header_value}
244 )['headers']['Custom-Header'],
245 header_value,
246 'custom header 2',
247 )
248
249 header_value = 'X' * 4096
250 self.assertEqual(
251 self.get_http10(
252 headers={'Host': 'localhost', 'Custom-Header': header_value}
253 )['headers']['Custom-Header'],
254 header_value,
255 'custom header 3',
256 )
257
258 header_value = 'X' * 8191
259 self.assertEqual(
260 self.get_http10(
261 headers={'Host': 'localhost', 'Custom-Header': header_value}
262 )['headers']['Custom-Header'],
263 header_value,
264 'custom header 4',
265 )
266
267 header_value = 'X' * 8192
268 self.assertEqual(
269 self.get_http10(
270 headers={'Host': 'localhost', 'Custom-Header': header_value}
271 )['status'],
272 431,
273 'custom header 5',
274 )
275
276 def test_proxy_fragmented(self):
277 _, sock = self.http(
278 b"""GET / HTT""", raw=True, start=True, no_recv=True
279 )
280
281 time.sleep(1)
282
283 sock.sendall("P/1.0\r\nHost: localhos".encode())
284
285 time.sleep(1)
286
287 sock.sendall("t\r\n\r\n".encode())
288
289 self.assertRegex(
290 self.recvall(sock).decode(), '200 OK', 'fragmented send'
291 )
292 sock.close()
293
294 def test_proxy_fragmented_close(self):
295 _, sock = self.http(
296 b"""GET / HTT""", raw=True, start=True, no_recv=True
297 )
298
299 time.sleep(1)

--- 23 unchanged lines hidden (view full) ---

323
324 time.sleep(1)
325
326 sock.sendall(("X" * 10000).encode())
327
328 resp = self._resp_to_dict(self.recvall(sock).decode())
329 sock.close()
330
331 self.assertEqual(resp['status'], 200, 'status')
332 self.assertEqual(resp['body'], "X" * 30000, 'body')
333
334 def test_proxy_fragmented_body_close(self):
335 _, sock = self.http(
336 b"""GET / HTT""", raw=True, start=True, no_recv=True
337 )
338
339 time.sleep(1)
340
341 sock.sendall("P/1.0\r\nHost: localhost\r\n".encode())
342 sock.sendall("Content-Length: 30000\r\n".encode())
343
344 time.sleep(1)
345
346 sock.sendall("\r\n".encode())
347 sock.sendall(("X" * 10000).encode())
348
349 sock.close()
350
351 def test_proxy_nowhere(self):
352 self.assertIn(
353 'success',
354 self.conf(
355 [{"action": {"proxy": "http://127.0.0.1:7082"}}], 'routes'
356 ),
357 'proxy path changed',
358 )
359
360 self.assertEqual(self.get_http10()['status'], 502, 'status')
361
362 def test_proxy_ipv6(self):
363 self.assertIn(
364 'success',
365 self.conf(
366 {
367 "*:7080": {"pass": "routes"},
368 "[::1]:7081": {'application': 'mirror'},
369 },
370 'listeners',
371 ),
372 'add ipv6 listener configure',
373 )
374
375 self.assertIn(
376 'success',
377 self.conf([{"action": {"proxy": "http://[::1]:7081"}}], 'routes'),
378 'proxy ipv6 configure',
379 )
380
381 self.assertEqual(self.get_http10()['status'], 200, 'status')
382
383 def test_proxy_unix(self):
384 addr = self.testdir + '/sock'
385
386 self.assertIn(
387 'success',
388 self.conf(
389 {
390 "*:7080": {"pass": "routes"},
391 "unix:" + addr: {'application': 'mirror'},
392 },
393 'listeners',
394 ),
395 'add unix listener configure',
396 )
397
398 self.assertIn(
399 'success',
400 self.conf(
401 [{"action": {"proxy": 'http://unix:' + addr}}], 'routes'
402 ),
403 'proxy unix configure',
404 )
405
406 self.assertEqual(self.get_http10()['status'], 200, 'status')
407
408 def test_proxy_delayed(self):
409 self.assertIn(
410 'success',
411 self.conf(
412 {"pass": "applications/delayed"}, 'listeners/*:7081'
413 ),
414 'delayed configure',
415 )
416
417 body = '0123456789' * 1000
418 resp = self.post_http10(
419 headers={
420 'Host': 'localhost',
421 'Content-Type': 'text/html',
422 'Content-Length': str(len(body)),
423 'X-Parts': '2',
424 'X-Delay': '1',
425 },
426 body=body,
427 )
428
429 self.assertEqual(resp['status'], 200, 'status')
430 self.assertEqual(resp['body'], body, 'body')
431
432 resp = self.post_http10(
433 headers={
434 'Host': 'localhost',
435 'Content-Type': 'text/html',
436 'Content-Length': str(len(body)),
437 'X-Parts': '2',
438 'X-Delay': '1',
439 },
440 body=body,
441 )
442
443 self.assertEqual(resp['status'], 200, 'status')
444 self.assertEqual(resp['body'], body, 'body')
445
446 def test_proxy_delayed_close(self):
447 self.assertIn(
448 'success',
449 self.conf(
450 {"pass": "applications/delayed"}, 'listeners/*:7081'
451 ),
452 'delayed configure',
453 )
454
455 _, sock = self.post_http10(
456 headers={
457 'Host': 'localhost',
458 'Content-Type': 'text/html',
459 'Content-Length': '10000',
460 'X-Parts': '3',
461 'X-Delay': '1',
462 },
463 body='0123456789' * 1000,
464 start=True,
465 no_recv=True,
466 )
467
468 self.assertRegex(
469 sock.recv(100).decode(), '200 OK', 'first'
470 )
471 sock.close()
472
473 _, sock = self.post_http10(
474 headers={
475 'Host': 'localhost',
476 'Content-Type': 'text/html',
477 'Content-Length': '10000',
478 'X-Parts': '3',
479 'X-Delay': '1',
480 },
481 body='0123456789' * 1000,
482 start=True,
483 no_recv=True,
484 )
485
486 self.assertRegex(
487 sock.recv(100).decode(), '200 OK', 'second'
488 )
489 sock.close()
490
491 @unittest.skip('not yet')
492 def test_proxy_content_length(self):
493 self.assertIn(
494 'success',
495 self.conf(
496 [
497 {
498 "action": {
499 "proxy": "http://127.0.0.1:"
500 + str(self.SERVER_PORT)
501 }
502 }
503 ],
504 'routes',
505 ),
506 'proxy backend configure',
507 )
508
509 resp = self.get_http10()
510 self.assertEqual(len(resp['body']), 0, 'body lt Content-Length 0')
511
512 resp = self.get_http10(headers={'Host': 'localhost', 'X-Len': '5'})
513 self.assertEqual(len(resp['body']), 5, 'body lt Content-Length 5')
514
515 resp = self.get_http10(headers={'Host': 'localhost', 'X-Len': '9'})
516 self.assertEqual(len(resp['body']), 9, 'body lt Content-Length 9')
517
518 resp = self.get_http10(headers={'Host': 'localhost', 'X-Len': '11'})
519 self.assertEqual(len(resp['body']), 10, 'body gt Content-Length 11')
520
521 resp = self.get_http10(headers={'Host': 'localhost', 'X-Len': '15'})
522 self.assertEqual(len(resp['body']), 10, 'body gt Content-Length 15')
523
524 def test_proxy_invalid(self):
525 def check_proxy(proxy):
526 self.assertIn(
527 'error',
528 self.conf([{"action": {"proxy": proxy}}], 'routes'),
529 'proxy invalid',
530 )
531
532 check_proxy('blah')
533 check_proxy('/blah')
534 check_proxy('unix:/blah')
535 check_proxy('http://blah')
536 check_proxy('http://127.0.0.1')
537 check_proxy('http://127.0.0.1:')
538 check_proxy('http://127.0.0.1:blah')
539 check_proxy('http://127.0.0.1:-1')
540 check_proxy('http://127.0.0.1:7080b')
541 check_proxy('http://[]')
542 check_proxy('http://[]:7080')
543 check_proxy('http://[:]:7080')
544 check_proxy('http://[::7080')
545
546 def test_proxy_loop(self):
547 self.skip_alerts.extend(
548 [
549 r'socket.*failed',
550 r'accept.*failed',
551 r'new connections are not accepted',
552 ]
553 )
554 self.conf(
555 {
556 "listeners": {
557 "*:7080": {"pass": "routes"},
558 "*:7081": {"pass": "applications/mirror"},
559 "*:7082": {"pass": "routes"},
560 },
561 "routes": [{"action": {"proxy": "http://127.0.0.1:7082"}}],
562 "applications": {
563 "mirror": {
564 "type": "python",
565 "processes": {"spare": 0},
566 "path": self.current_dir + "/python/mirror",
567 "working_directory": self.current_dir
568 + "/python/mirror",
569 "module": "wsgi",
570 },
571 },
572 }
573 )
574
575 self.get_http10(no_recv=True)
576 self.get_http10(read_timeout=1)
577
578if __name__ == '__main__':
579 TestProxy.main()