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