1import re 2import socket 3import subprocess 4import time 5 6import pytest 7 8from unit.applications.lang.python import ApplicationPython 9 10prerequisites = {'modules': {'python': 'any'}} 11 12client = ApplicationPython() 13 14 15def sysctl(): 16 try: 17 out = subprocess.check_output( 18 ['sysctl', '-a'], stderr=subprocess.STDOUT 19 ).decode() 20 except FileNotFoundError: 21 pytest.skip('requires sysctl') 22 23 return out 24 25 26def test_settings_large_header_buffer_size(): 27 client.load('empty') 28 29 def set_buffer_size(size): 30 assert 'success' in client.conf( 31 {'http': {'large_header_buffer_size': size}}, 32 'settings', 33 ) 34 35 def header_value(size, expect=200): 36 headers = {'Host': 'a' * (size - 1), 'Connection': 'close'} 37 assert client.get(headers=headers)['status'] == expect 38 39 set_buffer_size(4096) 40 header_value(4096) 41 header_value(4097, 431) 42 43 set_buffer_size(16384) 44 header_value(16384) 45 header_value(16385, 431) 46 47 48def test_settings_large_header_buffers(): 49 client.load('empty') 50 51 def set_buffers(buffers): 52 assert 'success' in client.conf( 53 {'http': {'large_header_buffers': buffers}}, 54 'settings', 55 ) 56 57 def big_headers(headers_num, expect=200): 58 headers = {'Host': 'localhost', 'Connection': 'close'} 59 60 for i in range(headers_num): 61 headers[f'Custom-header-{i}'] = 'a' * 8000 62 63 assert client.get(headers=headers)['status'] == expect 64 65 set_buffers(1) 66 big_headers(1) 67 big_headers(2, 431) 68 69 set_buffers(2) 70 big_headers(2) 71 big_headers(3, 431) 72 73 set_buffers(8) 74 big_headers(8) 75 big_headers(9, 431) 76 77 78@pytest.mark.skip('not yet') 79def test_settings_large_header_buffer_invalid(): 80 def check_error(conf): 81 assert 'error' in client.conf({'http': conf}, 'settings') 82 83 check_error({'large_header_buffer_size': -1}) 84 check_error({'large_header_buffer_size': 0}) 85 check_error({'large_header_buffers': -1}) 86 check_error({'large_header_buffers': 0}) 87 88 89def test_settings_server_version(): 90 client.load('empty') 91 92 assert client.get()['headers']['Server'].startswith('Unit/') 93 94 assert 'success' in client.conf( 95 {"http": {"server_version": False}}, 'settings' 96 ), 'remove version' 97 assert client.get()['headers']['Server'] == 'Unit' 98 99 assert 'success' in client.conf( 100 {"http": {"server_version": True}}, 'settings' 101 ), 'add version' 102 assert client.get()['headers']['Server'].startswith('Unit/') 103 104 105def test_settings_header_read_timeout(): 106 client.load('empty') 107 108 def req(): 109 (_, sock) = client.http( 110 b"""GET / HTTP/1.1 111""", 112 start=True, 113 read_timeout=1, 114 raw=True, 115 ) 116 117 time.sleep(3) 118 119 return client.http( 120 b"""Host: localhost 121Connection: close 122 123""", 124 sock=sock, 125 raw=True, 126 ) 127 128 assert 'success' in client.conf( 129 {'http': {'header_read_timeout': 2}}, 'settings' 130 ) 131 assert req()['status'] == 408, 'status header read timeout' 132 133 assert 'success' in client.conf( 134 {'http': {'header_read_timeout': 7}}, 'settings' 135 ) 136 assert req()['status'] == 200, 'status header read timeout 2' 137 138 139def test_settings_header_read_timeout_update(): 140 client.load('empty') 141 142 assert 'success' in client.conf( 143 {'http': {'header_read_timeout': 4}}, 'settings' 144 ) 145 146 sock = client.http( 147 b"""GET / HTTP/1.1 148""", 149 raw=True, 150 no_recv=True, 151 ) 152 153 time.sleep(2) 154 155 sock = client.http( 156 b"""Host: localhost 157""", 158 sock=sock, 159 raw=True, 160 no_recv=True, 161 ) 162 163 time.sleep(2) 164 165 (resp, sock) = client.http( 166 b"""X-Blah: blah 167""", 168 start=True, 169 sock=sock, 170 read_timeout=1, 171 raw=True, 172 ) 173 174 if len(resp) != 0: 175 sock.close() 176 177 else: 178 time.sleep(2) 179 180 resp = client.http( 181 b"""Connection: close 182 183""", 184 sock=sock, 185 raw=True, 186 ) 187 188 assert resp['status'] == 408, 'status header read timeout update' 189 190 191def test_settings_body_read_timeout(): 192 client.load('empty') 193 194 def req(): 195 (_, sock) = client.http( 196 b"""POST / HTTP/1.1 197Host: localhost 198Content-Length: 10 199Connection: close 200 201""", 202 start=True, 203 raw_resp=True, 204 read_timeout=1, 205 raw=True, 206 ) 207 208 time.sleep(3) 209 210 return client.http(b"""0123456789""", sock=sock, raw=True) 211 212 assert 'success' in client.conf( 213 {'http': {'body_read_timeout': 2}}, 'settings' 214 ) 215 assert req()['status'] == 408, 'status body read timeout' 216 217 assert 'success' in client.conf( 218 {'http': {'body_read_timeout': 7}}, 'settings' 219 ) 220 assert req()['status'] == 200, 'status body read timeout 2' 221 222 223def test_settings_body_read_timeout_update(): 224 client.load('empty') 225 226 assert 'success' in client.conf( 227 {'http': {'body_read_timeout': 4}}, 'settings' 228 ) 229 230 (resp, sock) = client.http( 231 b"""POST / HTTP/1.1 232Host: localhost 233Content-Length: 10 234Connection: close 235 236""", 237 start=True, 238 read_timeout=1, 239 raw=True, 240 ) 241 242 time.sleep(2) 243 244 (resp, sock) = client.http( 245 b"""012""", start=True, sock=sock, read_timeout=1, raw=True 246 ) 247 248 time.sleep(2) 249 250 (resp, sock) = client.http( 251 b"""345""", start=True, sock=sock, read_timeout=1, raw=True 252 ) 253 254 time.sleep(2) 255 256 resp = client.http(b"""6789""", sock=sock, raw=True) 257 258 assert resp['status'] == 200, 'status body read timeout update' 259 260 261def test_settings_send_timeout(temp_dir): 262 client.load('body_generate') 263 264 def req(addr, data_len): 265 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 266 sock.connect(addr) 267 268 req = f"""GET / HTTP/1.1 269Host: localhost 270X-Length: {data_len} 271Connection: close 272 273""" 274 275 sock.sendall(req.encode()) 276 277 data = sock.recv(16).decode() 278 279 time.sleep(3) 280 281 data += client.recvall(sock).decode() 282 283 sock.close() 284 285 return data 286 287 sysctl_out = sysctl() 288 values = re.findall(r'net.core.[rw]mem_(?:max|default).*?(\d+)', sysctl_out) 289 values = [int(v) for v in values] 290 291 data_len = 1048576 if len(values) == 0 else 10 * max(values) 292 293 addr = f'{temp_dir}/sock' 294 295 assert 'success' in client.conf( 296 {f'unix:{addr}': {'application': 'body_generate'}}, 'listeners' 297 ) 298 299 assert 'success' in client.conf({'http': {'send_timeout': 1}}, 'settings') 300 301 data = req(addr, data_len) 302 assert re.search(r'200 OK', data), 'send timeout status' 303 assert len(data) < data_len, 'send timeout data ' 304 305 client.conf({'http': {'send_timeout': 7}}, 'settings') 306 307 data = req(addr, data_len) 308 assert re.search(r'200 OK', data), 'send timeout status 2' 309 assert len(data) > data_len, 'send timeout data 2' 310 311 312def test_settings_idle_timeout(): 313 client.load('empty') 314 315 def req(): 316 (_, sock) = client.get( 317 headers={'Host': 'localhost', 'Connection': 'keep-alive'}, 318 start=True, 319 read_timeout=1, 320 ) 321 322 time.sleep(3) 323 324 return client.get(sock=sock) 325 326 assert client.get()['status'] == 200, 'init' 327 328 assert 'success' in client.conf({'http': {'idle_timeout': 2}}, 'settings') 329 assert req()['status'] == 408, 'status idle timeout' 330 331 assert 'success' in client.conf({'http': {'idle_timeout': 7}}, 'settings') 332 assert req()['status'] == 200, 'status idle timeout 2' 333 334 335def test_settings_idle_timeout_2(): 336 client.load('empty') 337 338 def req(): 339 sock = client.http(b'', raw=True, no_recv=True) 340 341 time.sleep(3) 342 343 return client.get(sock=sock) 344 345 assert client.get()['status'] == 200, 'init' 346 347 assert 'success' in client.conf({'http': {'idle_timeout': 1}}, 'settings') 348 assert req()['status'] == 408, 'status idle timeout' 349 350 assert 'success' in client.conf({'http': {'idle_timeout': 7}}, 'settings') 351 assert req()['status'] == 200, 'status idle timeout 2' 352 353 354def test_settings_max_body_size(): 355 client.load('empty') 356 357 assert 'success' in client.conf({'http': {'max_body_size': 5}}, 'settings') 358 359 assert client.post(body='01234')['status'] == 200, 'status size' 360 assert client.post(body='012345')['status'] == 413, 'status size max' 361 362 363def test_settings_max_body_size_large(): 364 client.load('mirror') 365 366 assert 'success' in client.conf( 367 {'http': {'max_body_size': 32 * 1024 * 1024}}, 'settings' 368 ) 369 370 body = '0123456789abcdef' * 4 * 64 * 1024 371 resp = client.post(body=body, read_buffer_size=1024 * 1024) 372 assert resp['status'] == 200, 'status size 4' 373 assert resp['body'] == body, 'status body 4' 374 375 body = '0123456789abcdef' * 8 * 64 * 1024 376 resp = client.post(body=body, read_buffer_size=1024 * 1024) 377 assert resp['status'] == 200, 'status size 8' 378 assert resp['body'] == body, 'status body 8' 379 380 body = '0123456789abcdef' * 16 * 64 * 1024 381 resp = client.post(body=body, read_buffer_size=1024 * 1024) 382 assert resp['status'] == 200, 'status size 16' 383 assert resp['body'] == body, 'status body 16' 384 385 body = '0123456789abcdef' * 32 * 64 * 1024 386 resp = client.post(body=body, read_buffer_size=1024 * 1024) 387 assert resp['status'] == 200, 'status size 32' 388 assert resp['body'] == body, 'status body 32' 389 390 391@pytest.mark.skip('not yet') 392def test_settings_negative_value(): 393 assert 'error' in client.conf( 394 {'http': {'max_body_size': -1}}, 'settings' 395 ), 'settings negative value' 396 397 398def test_settings_body_buffer_size(): 399 client.load('mirror') 400 401 assert 'success' in client.conf( 402 { 403 'http': { 404 'max_body_size': 64 * 1024 * 1024, 405 'body_buffer_size': 32 * 1024 * 1024, 406 } 407 }, 408 'settings', 409 ) 410 411 body = '0123456789abcdef' 412 resp = client.post(body=body) 413 assert bool(resp), 'response from application' 414 assert resp['status'] == 200, 'status' 415 assert resp['body'] == body, 'body' 416 417 body = '0123456789abcdef' * 1024 * 1024 418 resp = client.post(body=body, read_buffer_size=1024 * 1024) 419 assert bool(resp), 'response from application 2' 420 assert resp['status'] == 200, 'status 2' 421 assert resp['body'] == body, 'body 2' 422 423 body = '0123456789abcdef' * 2 * 1024 * 1024 424 resp = client.post(body=body, read_buffer_size=1024 * 1024) 425 assert bool(resp), 'response from application 3' 426 assert resp['status'] == 200, 'status 3' 427 assert resp['body'] == body, 'body 3' 428 429 body = '0123456789abcdef' * 3 * 1024 * 1024 430 resp = client.post(body=body, read_buffer_size=1024 * 1024) 431 assert bool(resp), 'response from application 4' 432 assert resp['status'] == 200, 'status 4' 433 assert resp['body'] == body, 'body 4' 434 435 436def test_settings_log_route(findall, search_in_file, wait_for_record): 437 def count_fallbacks(): 438 return len(findall(r'"fallback" taken')) 439 440 def check_record(template): 441 assert search_in_file(template) is not None 442 443 def check_no_record(template): 444 assert search_in_file(template) is None 445 446 def template_req_line(url): 447 return rf'\[notice\].*http request line "GET {url} HTTP/1\.1"' 448 449 def template_selected(route): 450 return rf'\[notice\].*"{route}" selected' 451 452 def template_discarded(route): 453 return rf'\[info\].*"{route}" discarded' 454 455 def wait_for_request_log(status, uri, route): 456 assert client.get(url=uri)['status'] == status 457 assert wait_for_record(template_req_line(uri)) is not None 458 assert wait_for_record(template_selected(route)) is not None 459 460 # routes array 461 462 assert 'success' in client.conf( 463 { 464 "listeners": {"*:8080": {"pass": "routes"}}, 465 "routes": [ 466 { 467 "match": { 468 "uri": "/zero", 469 }, 470 "action": {"return": 200}, 471 }, 472 { 473 "action": {"return": 201}, 474 }, 475 ], 476 "applications": {}, 477 "settings": {"http": {"log_route": True}}, 478 } 479 ) 480 481 wait_for_request_log(200, '/zero', 'routes/0') 482 check_no_record(r'discarded') 483 484 wait_for_request_log(201, '/one', 'routes/1') 485 check_record(template_discarded('routes/0')) 486 487 # routes object 488 489 assert 'success' in client.conf( 490 { 491 "listeners": {"*:8080": {"pass": "routes/main"}}, 492 "routes": { 493 "main": [ 494 { 495 "match": { 496 "uri": "/named_route", 497 }, 498 "action": {"return": 200}, 499 }, 500 { 501 "action": {"return": 201}, 502 }, 503 ] 504 }, 505 "applications": {}, 506 "settings": {"http": {"log_route": True}}, 507 } 508 ) 509 510 wait_for_request_log(200, '/named_route', 'routes/main/0') 511 check_no_record(template_discarded('routes/main')) 512 513 wait_for_request_log(201, '/unnamed_route', 'routes/main/1') 514 check_record(template_discarded('routes/main/0')) 515 516 # routes sequence 517 518 assert 'success' in client.conf( 519 { 520 "listeners": {"*:8080": {"pass": "routes/first"}}, 521 "routes": { 522 "first": [ 523 { 524 "action": {"pass": "routes/second"}, 525 }, 526 ], 527 "second": [ 528 { 529 "action": {"return": 200}, 530 }, 531 ], 532 }, 533 "applications": {}, 534 "settings": {"http": {"log_route": True}}, 535 } 536 ) 537 538 wait_for_request_log(200, '/sequence', 'routes/second/0') 539 check_record(template_selected('routes/first/0')) 540 541 # fallback 542 543 assert 'success' in client.conf( 544 { 545 "listeners": {"*:8080": {"pass": "routes/fall"}}, 546 "routes": { 547 "fall": [ 548 { 549 "action": { 550 "share": "/blah", 551 "fallback": {"pass": "routes/fall2"}, 552 }, 553 }, 554 ], 555 "fall2": [ 556 { 557 "action": {"return": 200}, 558 }, 559 ], 560 }, 561 "applications": {}, 562 "settings": {"http": {"log_route": True}}, 563 } 564 ) 565 566 wait_for_request_log(200, '/', 'routes/fall2/0') 567 assert count_fallbacks() == 1 568 check_record(template_selected('routes/fall/0')) 569 570 assert client.head()['status'] == 200 571 assert count_fallbacks() == 2 572 573 # disable log 574 575 assert 'success' in client.conf({"log_route": False}, 'settings/http') 576 577 url = '/disable_logging' 578 assert client.get(url=url)['status'] == 200 579 580 time.sleep(1) 581 582 check_no_record(template_req_line(url)) 583 584 # total 585 586 assert len(findall(r'\[notice\].*http request line')) == 7 587 assert len(findall(r'\[notice\].*selected')) == 10 588 assert len(findall(r'\[info\].*discarded')) == 2 589