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