Tests: parsing of "Transfer-Encoding: chunked" responses.
This commit is contained in:
@@ -141,7 +141,7 @@ class TestNodeApplication(TestApplicationNode):
|
|||||||
self.load('write_buffer')
|
self.load('write_buffer')
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.get()['body'], '6\r\nbuffer\r\n0\r\n\r\n', 'write buffer'
|
self.get()['body'], 'buffer', 'write buffer'
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_node_application_write_callback(self):
|
def test_node_application_write_callback(self):
|
||||||
@@ -149,7 +149,7 @@ class TestNodeApplication(TestApplicationNode):
|
|||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.get()['body'],
|
self.get()['body'],
|
||||||
'5\r\nhello\r\n5\r\nworld\r\n0\r\n\r\n',
|
'helloworld',
|
||||||
'write callback order',
|
'write callback order',
|
||||||
)
|
)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
@@ -173,7 +173,7 @@ class TestNodeApplication(TestApplicationNode):
|
|||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.get()['body'],
|
self.get()['body'],
|
||||||
'4\r\nbody\r\n4\r\ntrue\r\n0\r\n\r\n',
|
'bodytrue',
|
||||||
'write return',
|
'write return',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ class TestPerlApplication(TestApplicationPerl):
|
|||||||
def test_perl_application_body_empty(self):
|
def test_perl_application_body_empty(self):
|
||||||
self.load('body_empty')
|
self.load('body_empty')
|
||||||
|
|
||||||
self.assertEqual(self.get()['body'], '0\r\n\r\n', 'body empty')
|
self.assertEqual(self.get()['body'], '', 'body empty')
|
||||||
|
|
||||||
def test_perl_application_body_array(self):
|
def test_perl_application_body_array(self):
|
||||||
self.load('body_array')
|
self.load('body_array')
|
||||||
|
|||||||
@@ -540,7 +540,7 @@ Connection: close
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.assertEqual(resp['status'], 200, 'status')
|
self.assertEqual(resp['status'], 200, 'status')
|
||||||
self.assertEqual(resp['body'][-5:], '0\r\n\r\n', 'body')
|
self.assertEqual(resp['body'], 'XXXXXXX', 'body')
|
||||||
|
|
||||||
# Exception before start_response().
|
# Exception before start_response().
|
||||||
|
|
||||||
@@ -607,12 +607,11 @@ Connection: close
|
|||||||
'X-Skip': '2',
|
'X-Skip': '2',
|
||||||
'X-Chunked': '1',
|
'X-Chunked': '1',
|
||||||
'Connection': 'close',
|
'Connection': 'close',
|
||||||
}
|
},
|
||||||
)
|
raw_resp=True
|
||||||
if 'body' in resp:
|
|
||||||
self.assertNotEqual(
|
|
||||||
resp['body'][-5:], '0\r\n\r\n', 'incomplete body'
|
|
||||||
)
|
)
|
||||||
|
if resp:
|
||||||
|
self.assertNotEqual(resp[-5:], '0\r\n\r\n', 'incomplete body')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
len(self.findall(r'Traceback')), 4, 'traceback count 4'
|
len(self.findall(r'Traceback')), 4, 'traceback count 4'
|
||||||
)
|
)
|
||||||
@@ -646,12 +645,11 @@ Connection: close
|
|||||||
'X-Skip': '3',
|
'X-Skip': '3',
|
||||||
'X-Chunked': '1',
|
'X-Chunked': '1',
|
||||||
'Connection': 'close',
|
'Connection': 'close',
|
||||||
}
|
},
|
||||||
)
|
raw_resp=True
|
||||||
if 'body' in resp:
|
|
||||||
self.assertNotEqual(
|
|
||||||
resp['body'][-5:], '0\r\n\r\n', 'incomplete body 2'
|
|
||||||
)
|
)
|
||||||
|
if resp:
|
||||||
|
self.assertNotEqual(resp[-5:], '0\r\n\r\n', 'incomplete body 2')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
len(self.findall(r'Traceback')), 6, 'traceback count 6'
|
len(self.findall(r'Traceback')), 6, 'traceback count 6'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ class TestRubyApplication(TestApplicationRuby):
|
|||||||
def test_ruby_application_body_empty(self):
|
def test_ruby_application_body_empty(self):
|
||||||
self.load('body_empty')
|
self.load('body_empty')
|
||||||
|
|
||||||
self.assertEqual(self.get()['body'], '0\r\n\r\n', 'body empty')
|
self.assertEqual(self.get()['body'], '', 'body empty')
|
||||||
|
|
||||||
def test_ruby_application_body_array(self):
|
def test_ruby_application_body_array(self):
|
||||||
self.load('body_array')
|
self.load('body_array')
|
||||||
|
|||||||
@@ -84,4 +84,4 @@ class TestFeatureIsolation(TestApplicationProto):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
def parsejson(self, data):
|
def parsejson(self, data):
|
||||||
return json.loads(data.split('\n')[1])
|
return json.loads(data)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
import json
|
||||||
import socket
|
import socket
|
||||||
import select
|
import select
|
||||||
from unit.main import TestUnit
|
from unit.main import TestUnit
|
||||||
@@ -86,23 +87,24 @@ class TestHTTP(TestUnit):
|
|||||||
|
|
||||||
sock.sendall(req)
|
sock.sendall(req)
|
||||||
|
|
||||||
|
encoding = 'utf-8' if 'encoding' not in kwargs else kwargs['encoding']
|
||||||
|
|
||||||
if TestUnit.detailed:
|
if TestUnit.detailed:
|
||||||
print('>>>')
|
print('>>>')
|
||||||
try:
|
try:
|
||||||
print(req.decode('utf-8', 'ignore'))
|
print(req.decode(encoding, 'ignore'))
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
print(req)
|
print(req)
|
||||||
|
|
||||||
resp = ''
|
resp = ''
|
||||||
|
|
||||||
if 'no_recv' not in kwargs:
|
if 'no_recv' not in kwargs:
|
||||||
enc = 'utf-8' if 'encoding' not in kwargs else kwargs['encoding']
|
|
||||||
read_timeout = (
|
read_timeout = (
|
||||||
30 if 'read_timeout' not in kwargs else kwargs['read_timeout']
|
30 if 'read_timeout' not in kwargs else kwargs['read_timeout']
|
||||||
)
|
)
|
||||||
resp = self.recvall(
|
resp = self.recvall(
|
||||||
sock, read_timeout=read_timeout, buff_size=read_buffer_size
|
sock, read_timeout=read_timeout, buff_size=read_buffer_size
|
||||||
).decode(enc)
|
).decode(encoding)
|
||||||
|
|
||||||
if TestUnit.detailed:
|
if TestUnit.detailed:
|
||||||
print('<<<')
|
print('<<<')
|
||||||
@@ -114,6 +116,12 @@ class TestHTTP(TestUnit):
|
|||||||
if 'raw_resp' not in kwargs:
|
if 'raw_resp' not in kwargs:
|
||||||
resp = self._resp_to_dict(resp)
|
resp = self._resp_to_dict(resp)
|
||||||
|
|
||||||
|
headers = resp.get('headers')
|
||||||
|
if headers and headers.get('Transfer-Encoding') == 'chunked':
|
||||||
|
resp['body'] = self._parse_chunked_body(resp['body']).decode(
|
||||||
|
encoding
|
||||||
|
)
|
||||||
|
|
||||||
if 'start' not in kwargs:
|
if 'start' not in kwargs:
|
||||||
sock.close()
|
sock.close()
|
||||||
return resp
|
return resp
|
||||||
@@ -151,7 +159,7 @@ class TestHTTP(TestUnit):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
def _resp_to_dict(self, resp):
|
def _resp_to_dict(self, resp):
|
||||||
m = re.search('(.*?\x0d\x0a?)\x0d\x0a?(.*)', resp, re.M | re.S)
|
m = re.search(r'(.*?\x0d\x0a?)\x0d\x0a?(.*)', resp, re.M | re.S)
|
||||||
|
|
||||||
if not m:
|
if not m:
|
||||||
return {}
|
return {}
|
||||||
@@ -162,12 +170,12 @@ class TestHTTP(TestUnit):
|
|||||||
headers_lines = p.findall(headers_text)
|
headers_lines = p.findall(headers_text)
|
||||||
|
|
||||||
status = re.search(
|
status = re.search(
|
||||||
'^HTTP\/\d\.\d\s(\d+)|$', headers_lines.pop(0)
|
r'^HTTP\/\d\.\d\s(\d+)|$', headers_lines.pop(0)
|
||||||
).group(1)
|
).group(1)
|
||||||
|
|
||||||
headers = {}
|
headers = {}
|
||||||
for line in headers_lines:
|
for line in headers_lines:
|
||||||
m = re.search('(.*)\:\s(.*)', line)
|
m = re.search(r'(.*)\:\s(.*)', line)
|
||||||
|
|
||||||
if m.group(1) not in headers:
|
if m.group(1) not in headers:
|
||||||
headers[m.group(1)] = m.group(2)
|
headers[m.group(1)] = m.group(2)
|
||||||
@@ -180,6 +188,48 @@ class TestHTTP(TestUnit):
|
|||||||
|
|
||||||
return {'status': int(status), 'headers': headers, 'body': body}
|
return {'status': int(status), 'headers': headers, 'body': body}
|
||||||
|
|
||||||
|
def _parse_chunked_body(self, raw_body):
|
||||||
|
if isinstance(raw_body, str):
|
||||||
|
raw_body = bytes(raw_body.encode())
|
||||||
|
|
||||||
|
crlf = b'\r\n'
|
||||||
|
chunks = raw_body.split(crlf)
|
||||||
|
|
||||||
|
if len(chunks) < 3:
|
||||||
|
self.fail('Invalid chunked body')
|
||||||
|
|
||||||
|
if chunks.pop() != b'':
|
||||||
|
self.fail('No CRLF at the end of the body')
|
||||||
|
|
||||||
|
try:
|
||||||
|
last_size = int(chunks[-2], 16)
|
||||||
|
except:
|
||||||
|
self.fail('Invalid zero size chunk')
|
||||||
|
|
||||||
|
if last_size != 0 or chunks[-1] != b'':
|
||||||
|
self.fail('Incomplete body')
|
||||||
|
|
||||||
|
body = b''
|
||||||
|
while len(chunks) >= 2:
|
||||||
|
try:
|
||||||
|
size = int(chunks.pop(0), 16)
|
||||||
|
except:
|
||||||
|
self.fail('Invalid chunk size %s' % str(size))
|
||||||
|
|
||||||
|
if size == 0:
|
||||||
|
self.assertEqual(len(chunks), 1, 'last zero size')
|
||||||
|
break
|
||||||
|
|
||||||
|
temp_body = crlf.join(chunks)
|
||||||
|
|
||||||
|
body += temp_body[:size]
|
||||||
|
|
||||||
|
temp_body = temp_body[size + len(crlf) :]
|
||||||
|
|
||||||
|
chunks = temp_body.split(crlf)
|
||||||
|
|
||||||
|
return body
|
||||||
|
|
||||||
def waitforsocket(self, port):
|
def waitforsocket(self, port):
|
||||||
ret = False
|
ret = False
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user