Try to handle incomplete HTTP. See #5401. Please test.

Approach: Keep filling the receive buffer until we have found enough data to extract the first HTTP header and body
This commit is contained in:
Stephan Erb 2009-11-12 21:23:10 +01:00
parent 641947719f
commit ee5eb8b546
2 changed files with 66 additions and 65 deletions

View file

@ -645,13 +645,15 @@ class NonBlockingHTTP(NonBlockingTCP):
if self.get_state() == PROXY_CONNECTING: if self.get_state() == PROXY_CONNECTING:
NonBlockingTCP._on_receive(self, data) NonBlockingTCP._on_receive(self, data)
return return
if not self.recvbuff:
# recvbuff empty - fresh HTTP message was received # append currently received data to HTTP msg in buffer
try: self.recvbuff = '%s%s' % (self.recvbuff or '', data)
statusline, headers, self.recvbuff = self.parse_http_message(data) statusline, headers, httpbody, self.recvbuff = self.parse_http_message(self.recvbuff)
except ValueError:
self.disconnect() if not (statusline and headers and httpbody):
log.debug('Received incomplete HTTP response')
return return
if statusline[1] != '200': if statusline[1] != '200':
log.error('HTTP Error: %s %s' % (statusline[1], statusline[2])) log.error('HTTP Error: %s %s' % (statusline[1], statusline[2]))
self.disconnect() self.disconnect()
@ -659,21 +661,14 @@ class NonBlockingHTTP(NonBlockingTCP):
self.expected_length = int(headers['Content-Length']) self.expected_length = int(headers['Content-Length'])
if 'Connection' in headers and headers['Connection'].strip()=='close': if 'Connection' in headers and headers['Connection'].strip()=='close':
self.close_current_connection = True self.close_current_connection = True
else:
#sth in recvbuff - append currently received data to HTTP msg in buffer
self.recvbuff = '%s%s' % (self.recvbuff, data)
if self.expected_length > len(self.recvbuff): if self.expected_length > len(httpbody):
# If we haven't received the whole HTTP mess yet, let's end the thread. # If we haven't received the whole HTTP mess yet, let's end the thread.
# It will be finnished from one of following recvs on plugged socket. # It will be finnished from one of following recvs on plugged socket.
log.info('not enough bytes in HTTP response - %d expected, %d got' % log.info('not enough bytes in HTTP response - %d expected, %d got' %
(self.expected_length, len(self.recvbuff))) (self.expected_length, len(self.recvbuff)))
return else:
# everything was received # everything was received
httpbody = self.recvbuff
self.recvbuff = ''
self.expected_length = 0 self.expected_length = 0
if not self.http_persistent or self.close_current_connection: if not self.http_persistent or self.close_current_connection:
@ -718,10 +713,16 @@ class NonBlockingHTTP(NonBlockingTCP):
httpbody - string with http body) httpbody - string with http body)
''' '''
message = message.replace('\r','') message = message.replace('\r','')
# Remove latest \n splitted = message.split('\n\n')
if message.endswith('\n'): if len(splitted) < 2:
message = message[:-1] # no complete http message. Keep filling the buffer until we find one
(header, httpbody) = message.split('\n\n', 1) buffer_rest = message
return ('', '', '', buffer_rest)
else:
(header, httpbody) = splitted[:2]
if httpbody.endswith('\n'):
httpbody = httpbody[:-1]
buffer_rest = "\n\n".join(splitted[2:])
header = header.split('\n') header = header.split('\n')
statusline = header[0].split(' ', 2) statusline = header[0].split(' ', 2)
header = header[1:] header = header[1:]
@ -729,7 +730,7 @@ class NonBlockingHTTP(NonBlockingTCP):
for dummy in header: for dummy in header:
row = dummy.split(' ', 1) row = dummy.split(' ', 1)
headers[row[0][:-1]] = row[1] headers[row[0][:-1]] = row[1]
return (statusline, headers, httpbody) return (statusline, headers, httpbody, buffer_rest)
class NonBlockingHTTPBOSH(NonBlockingHTTP): class NonBlockingHTTPBOSH(NonBlockingHTTP):

View file

@ -227,9 +227,10 @@ class TestNonBlockingHTTP(AbstractTransportTest):
data = "<test>Please don't fail!</test>" data = "<test>Please don't fail!</test>"
http_message = transport.build_http_message(data) http_message = transport.build_http_message(data)
statusline, headers, http_body = transport.parse_http_message( statusline, headers, http_body, buffer_rest = transport.parse_http_message(
http_message) http_message)
self.assertFalse(bool(buffer_rest))
self.assertTrue(statusline and isinstance(statusline, list)) self.assertTrue(statusline and isinstance(statusline, list))
self.assertTrue(headers and isinstance(headers, dict)) self.assertTrue(headers and isinstance(headers, dict))
self.assertEqual(data, http_body, msg='Input and output are different') self.assertEqual(data, http_body, msg='Input and output are different')
@ -250,26 +251,25 @@ class TestNonBlockingHTTP(AbstractTransportTest):
transport._on_receive(message) transport._on_receive(message)
self.assertTrue(self.have_received_expected(), msg='Failed: In one go') self.assertTrue(self.have_received_expected(), msg='Failed: In one go')
# FIXME: Not yet implemented. def test_receive_http_message_in_chunks(self):
# def test_receive_http_message_in_chunks(self): ''' Let _on_receive handle some chunked http messages '''
# ''' Let _on_receive handle some chunked http messages ''' transport = self._get_transport(self.bosh_http_dict)
# transport = self._get_transport(self.bosh_http_dict)
# header = ("HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" +
# header = ("HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" + "Content-Length: 88\r\n\r\n")
# "Content-Length: 88\r\n\r\n") payload = "<test>Please don't fail!</test>"
# payload = "<test>Please don't fail!</test>" body = "<body xmlns='http://jabber.org/protocol/httpbind'>%s</body>" \
# body = "<body xmlns='http://jabber.org/protocol/httpbind'>%s</body>" \ % payload
# % payload message = "%s%s" % (header, body)
# message = "%s%s" % (header, body)
# chunk1, chunk2, chunk3 = message[:20], message[20:73], message[73:]
# chunk1, chunk2, chunk3 = message[:20], message[20:73], message[73:] nextmessage_chunk = "\r\n\r\nHTTP/1.1 200 OK\r\nContent-Type: text/x"
# nextmessage_chunk = "\r\n\r\nHTTP/1.1 200 OK\r\nContent-Type: text/x" chunks = (chunk1, chunk2, chunk3, nextmessage_chunk)
# chunks = (chunk1, chunk2, chunk3, nextmessage_chunk)
# transport.onreceive(self.expect_receive(body, msg='Failed: In chunks'))
# transport.onreceive(self.expect_receive(body, msg='Failed: In chunks')) for chunk in chunks:
# for chunk in chunks: transport._on_receive(chunk)
# transport._on_receive(chunk) self.assertTrue(self.have_received_expected(), msg='Failed: In chunks')
# self.assertTrue(self.have_received_expected(), msg='Failed: In chunks')
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()