-
Notifications
You must be signed in to change notification settings - Fork 105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Long body in responses and requests #897
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
2c245bb
Add long body tests
vladtcvs 024bc29
Request tests with wrong length
vladtcvs d555dd0
responses tests
vladtcvs 2527f60
Fixes
vladtcvs 27e39a6
fixes after review
vladtcvs dcbf6b5
move saving config to file to test
vladtcvs f2b6bdf
func tests: remove extra commas unwanded by wrk
vankoven fbe7b9c
Fixes after review
vladtcvs 3a27d27
Fix handling asyncore and parsing error
vladtcvs c5db882
handle close
vladtcvs 4c9c77a
Don't measure number of connections during tests with invalid response
vladtcvs e2a0678
fix TestInvalidResponse
vladtcvs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
__all__ = ['tf_cfg', 'deproxy', 'nginx', 'tempesta', 'error', 'flacky', | ||
'analyzer', 'stateful', 'dmesg'] | ||
'analyzer', 'stateful', 'dmesg', 'wrk'] | ||
|
||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,6 @@ class ParseError(Exception): | |
class IncompliteMessage(ParseError): | ||
pass | ||
|
||
|
||
class HeaderCollection(object): | ||
""" | ||
A collection class for HTTP Headers. This class combines aspects of a list | ||
|
@@ -207,10 +206,10 @@ def __repr__(self): | |
class HttpMessage(object): | ||
__metaclass__ = abc.ABCMeta | ||
|
||
def __init__(self, message_text=None, body_parsing=True, body_void=False): | ||
def __init__(self, message_text=None, body_parsing=True, method="GET"): | ||
self.msg = '' | ||
self.method = method | ||
self.body_parsing = True | ||
self.body_void = body_void # For responses to HEAD requests | ||
self.headers = HeaderCollection() | ||
self.trailer = HeaderCollection() | ||
self.body = '' | ||
|
@@ -222,41 +221,44 @@ def parse_text(self, message_text, body_parsing=True): | |
self.body_parsing = body_parsing | ||
stream = StringIO(message_text) | ||
self.__parse(stream) | ||
self.__set_str_msg() | ||
self.build_message() | ||
|
||
def __parse(self, stream): | ||
self.parse_firstline(stream) | ||
self.parse_headers(stream) | ||
self.body = '' | ||
self.parse_body(stream) | ||
|
||
def __set_str_msg(self): | ||
def build_message(self): | ||
self.msg = str(self) | ||
|
||
@abc.abstractmethod | ||
def parse_firstline(self, stream): | ||
pass | ||
|
||
@abc.abstractmethod | ||
def parse_body(self, stream): | ||
pass | ||
|
||
def get_firstline(self): | ||
return '' | ||
|
||
def parse_headers(self, stream): | ||
self.headers = HeaderCollection.from_stream(stream) | ||
|
||
def parse_body(self, stream): | ||
if self.body_parsing and 'Transfer-Encoding' in self.headers: | ||
enc = self.headers['Transfer-Encoding'] | ||
option = enc.split(',')[-1] # take the last option | ||
def read_encoded_body(self, stream): | ||
""" RFC 7230. 3.3.3 #3 """ | ||
enc = self.headers['Transfer-Encoding'] | ||
option = enc.split(',')[-1] # take the last option | ||
|
||
if option.strip().lower() == 'chunked': | ||
self.read_chunked_body(stream) | ||
else: | ||
error.bug('Not implemented!') | ||
elif self.body_parsing and 'Content-Length' in self.headers: | ||
length = int(self.headers['Content-Length']) | ||
self.read_sized_body(stream, length) | ||
if option.strip().lower() == 'chunked': | ||
self.read_chunked_body(stream) | ||
else: | ||
self.body = stream.read() | ||
error.bug('Not implemented!') | ||
|
||
def read_rest_body(self, stream): | ||
""" RFC 7230. 3.3.3 #7 """ | ||
self.body = stream.read() | ||
|
||
def read_chunked_body(self, stream): | ||
while True: | ||
|
@@ -278,11 +280,10 @@ def read_chunked_body(self, stream): | |
# Parsing trailer will eat last CRLF | ||
self.parse_trailer(stream) | ||
|
||
def read_sized_body(self, stream, size): | ||
if self.body_void: | ||
return | ||
if size == 0: | ||
return | ||
def read_sized_body(self, stream): | ||
""" RFC 7230. 3.3.3 #5 """ | ||
size = int(self.headers['Content-Length']) | ||
|
||
self.body = stream.read(size) | ||
if len(self.body) != size: | ||
raise ParseError(("Wrong body size: expect %d but got %d!" | ||
|
@@ -379,6 +380,19 @@ def parse_firstline(self, stream): | |
def get_firstline(self): | ||
return ' '.join([self.method, self.uri, self.version]) | ||
|
||
def parse_body(self, stream): | ||
""" RFC 7230 3.3.3 """ | ||
# 3.3.3 3 | ||
if 'Transfer-Encoding' in self.headers: | ||
self.read_encoded_body(stream) | ||
return | ||
# 3.3.3 5 | ||
if 'Content-Length' in self.headers: | ||
self.read_sized_body(stream) | ||
return | ||
# 3.3.3 6 | ||
self.body = '' | ||
|
||
def __eq__(self, other): | ||
return ((self.method == other.method) | ||
and (self.version == other.version) | ||
|
@@ -422,6 +436,31 @@ def parse_firstline(self, stream): | |
except: | ||
raise ParseError('Invalid Status code!') | ||
|
||
def parse_body(self, stream): | ||
""" RFC 7230 3.3.3 """ | ||
# 3.3.3 1 | ||
if self.method == "HEAD": | ||
return | ||
code = int(self.status) | ||
if code >= 100 and code <= 199 or \ | ||
code == 204 or code == 304: | ||
return | ||
# 3.3.3 2 | ||
if self.method == "CONNECT" and code >= 200 and code <= 299: | ||
error.bug('Not implemented!') | ||
return | ||
# 3.3.3 3 | ||
if 'Transfer-Encoding' in self.headers: | ||
self.read_encoded_body(stream) | ||
return | ||
# TODO: check 3.3.3 4 | ||
# 3.3.3 5 | ||
if 'Content-Length' in self.headers: | ||
self.read_sized_body(stream) | ||
return | ||
# 3.3.3 7 | ||
self.read_rest_body(stream) | ||
|
||
def get_firstline(self): | ||
status = int(self.status) | ||
reason = BaseHTTPRequestHandler.responses[status][0] | ||
|
@@ -438,12 +477,12 @@ def __ne__(self, other): | |
|
||
@staticmethod | ||
def create(status, headers, version='HTTP/1.1', date=False, | ||
srv_version=None, body=None, body_void=False): | ||
srv_version=None, body=None, method='GET'): | ||
reason = BaseHTTPRequestHandler.responses | ||
first_line = ' '.join([version, str(status), reason[status][0]]) | ||
msg = HttpMessage.create(first_line, headers, date=date, | ||
srv_version=srv_version, body=body) | ||
return Response(msg, body_void=body_void) | ||
return Response(msg, method=method) | ||
|
||
#------------------------------------------------------------------------------- | ||
# HTTP Client/Server | ||
|
@@ -481,10 +520,10 @@ def run_start(self): | |
def clear(self): | ||
self.request_buffer = '' | ||
|
||
def set_request(self, request): | ||
if request: | ||
self.request = request | ||
self.request_buffer = request.msg | ||
def set_request(self, message_chain): | ||
if message_chain: | ||
self.request = message_chain.request | ||
self.request_buffer = message_chain.request.msg | ||
|
||
def set_tester(self, tester): | ||
self.tester = tester | ||
|
@@ -503,7 +542,7 @@ def handle_read(self): | |
tf_cfg.dbg(5, self.response_buffer) | ||
try: | ||
response = Response(self.response_buffer, | ||
body_void=(self.request.method == 'HEAD')) | ||
method=self.request.method) | ||
self.response_buffer = self.response_buffer[len(response.msg):] | ||
except IncompliteMessage: | ||
return | ||
|
@@ -512,6 +551,10 @@ def handle_read(self): | |
'<<<<<\n%s>>>>>' | ||
% self.response_buffer)) | ||
raise | ||
if len(self.response_buffer) > 0: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please check with |
||
# TODO: take care about pipelined case | ||
raise ParseError('Garbage after response end:\n```\n%s\n```\n' % \ | ||
self.response_buffer) | ||
if self.tester: | ||
self.tester.recieved_response(response) | ||
self.response_buffer = '' | ||
|
@@ -529,7 +572,11 @@ def handle_write(self): | |
|
||
def handle_error(self): | ||
_, v, _ = sys.exc_info() | ||
error.bug('\tDeproxy: Client: %s' % v) | ||
if type(v) == ParseError or type(v) == AssertionError: | ||
raise v | ||
else: | ||
error.bug('\tDeproxy: Client: %s' % v) | ||
|
||
|
||
|
||
class ServerConnection(asyncore.dispatcher_with_send): | ||
|
@@ -640,17 +687,24 @@ def handle_accept(self): | |
self.connections.append(handler) | ||
assert len(self.connections) <= self.conns_n, \ | ||
('Too lot connections, expect %d, got %d' | ||
& (self.conns_n, len(self.connections))) | ||
% (self.conns_n, len(self.connections))) | ||
|
||
def handle_read_event(self): | ||
asyncore.dispatcher.handle_read_event(self) | ||
|
||
def active_conns_n(self): | ||
return len(self.connections) | ||
|
||
def handle_error(self): | ||
_, v, _ = sys.exc_info() | ||
raise Exception('\tDeproxy: Server %s:%d: %s' % (self.ip, self.port, v)) | ||
if type(v) == AssertionError: | ||
raise v | ||
else: | ||
raise Exception('\tDeproxy: Server %s:%d: %s' % \ | ||
(self.ip, self.port, type(v))) | ||
|
||
def handle_close(self): | ||
self.stop() | ||
self.close() | ||
|
||
|
||
#------------------------------------------------------------------------------- | ||
|
@@ -732,7 +786,7 @@ def run(self): | |
for self.current_chain in self.message_chains: | ||
self.recieved_chain = MessageChain.empty() | ||
self.client.clear() | ||
self.client.set_request(self.current_chain.request) | ||
self.client.set_request(self.current_chain) | ||
self.loop() | ||
self.check_expectations() | ||
|
||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is many cases when response may not have body: 302 responses, 200 responses to CONNECT and so on. I think that previous flag suit better in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
independent flag can generate inconsistency. We don't have 302 and CONNECT tests now, and when we write such tests, we should add this cases here