Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5632fb7
Accumulate H1 request and response bodies in a buffer during parse, a…
antoniovicente Jan 15, 2020
f8e5d34
add test for invalid chunk header processing
antoniovicente Mar 16, 2020
44e59f6
Change argument name to hack around name conflict issue with namespac…
antoniovicente Mar 16, 2020
e7096f4
Add missing header to fix bazel compile_time_options CI
antoniovicente Mar 17, 2020
9de08cc
use absl::string_view
antoniovicente Mar 17, 2020
dbf66e9
Merge remote-tracking branch 'upstream/master' into h1_dispatch_buffe…
antoniovicente Mar 23, 2020
26340ac
improve e2e coverage for larger request/responses over H1 and H2
antoniovicente Mar 23, 2020
4508697
cover large transfers with content-length
antoniovicente Mar 23, 2020
ae92586
address review comments
antoniovicente Mar 24, 2020
6739dfc
Remove http_benchmark
antoniovicente Mar 24, 2020
334847f
make spellchecker happy
antoniovicente Mar 24, 2020
fa7b117
Merge remote-tracking branch 'upstream/master' into h1_dispatch_buffe…
antoniovicente Mar 25, 2020
f419bf1
add invalid chunk test
antoniovicente Mar 25, 2020
ddc5a01
Address review comments:
antoniovicente Mar 25, 2020
0101521
Tighten test that verifies that chunked body is delivered even if an …
antoniovicente Mar 25, 2020
631ea09
expand comments
antoniovicente Mar 26, 2020
8a0d1fb
fix clang-tidy
antoniovicente Mar 26, 2020
91ff8d6
Kick CI
antoniovicente Mar 30, 2020
29aa428
Merge remote-tracking branch 'upstream/master' into h1_dispatch_buffe…
antoniovicente Mar 31, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 43 additions & 18 deletions source/common/http/http1/codec_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -388,15 +388,19 @@ http_parser_settings ConnectionImpl::settings_{
return static_cast<ConnectionImpl*>(parser->data)->onHeadersCompleteBase();
},
[](http_parser* parser, const char* at, size_t length) -> int {
static_cast<ConnectionImpl*>(parser->data)->onBody(at, length);
static_cast<ConnectionImpl*>(parser->data)->bufferBody(at, length);
return 0;
},
[](http_parser* parser) -> int {
static_cast<ConnectionImpl*>(parser->data)->onMessageCompleteBase();
return 0;
},
nullptr, // on_chunk_header
nullptr // on_chunk_complete
[](http_parser* parser) -> int {
const bool is_final_chunk = (parser->content_length == 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no no, content length is the length of the body, see?
https://github.com/nodejs/http-parser/blob/master/http_parser.h#L307

.....
reads further: https://github.com/nodejs/http-parser/blob/master/http_parser.h#L337
.....

never mind :-(
optionally, maybe comment this field is overloaded, in case other people have concerns?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment. Thanks!

static_cast<ConnectionImpl*>(parser->data)->onChunkHeader(is_final_chunk);
return 0;
},
nullptr // on_chunk_complete
};

ConnectionImpl::ConnectionImpl(Network::Connection& connection, Stats::Scope& stats,
Expand Down Expand Up @@ -450,18 +454,15 @@ bool ConnectionImpl::maybeDirectDispatch(Buffer::Instance& data) {
return false;
}

ssize_t total_parsed = 0;
for (const Buffer::RawSlice& slice : data.getRawSlices()) {
total_parsed += slice.len_;
onBody(static_cast<const char*>(slice.mem_), slice.len_);
}
ENVOY_CONN_LOG(trace, "direct-dispatched {} bytes", connection_, total_parsed);
data.drain(total_parsed);
ENVOY_CONN_LOG(trace, "direct-dispatched {} bytes", connection_, data.length());
onBody(data);
data.drain(data.length());
return true;
}

void ConnectionImpl::dispatch(Buffer::Instance& data) {
ENVOY_CONN_LOG(trace, "parsing {} bytes", connection_, data.length());
ASSERT(buffered_body_.length() == 0);

if (maybeDirectDispatch(data)) {
return;
Expand All @@ -474,10 +475,15 @@ void ConnectionImpl::dispatch(Buffer::Instance& data) {
if (data.length() > 0) {
for (const Buffer::RawSlice& slice : data.getRawSlices()) {
total_parsed += dispatchSlice(static_cast<const char*>(slice.mem_), slice.len_);
if (HTTP_PARSER_ERRNO(&parser_) != HPE_OK) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have tests for garbled chunks which would make sure we don't dispatch buffered body when we fail parsing mid-dispatch? Or do we dispatch given the break rather than return? I'd think we would want to halt work.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parser can only be in 2 states when we hit this statement: HPE_OK or HPE_PAUSED.
The reason is that other error codes throw an exception in ConnectionImpl::dispatchSlice which unrolls the stack to the HCM.

I had written an invalid chunk headers test to cover this, but it seems to have disappeared due to a bad merge... See codec_impl_test.cc Http1ServerConnectionImplTest.InvalidChunkHeader

Thanks for catching this!

break;
}
}
dispatchBufferedBody();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we hit a parser error above and HTTP_PARSER_ERRNO(&parser_) != HPE_OK don't we break out of the for loop and then fail the assert in dispatchBufferedBody?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ASSERT is avoided due to exceptions. As we remove exceptions, the ASSERT should help us make sure we end up handling this case correctly.

} else {
dispatchSlice(nullptr, 0);
}
ASSERT(buffered_body_.length() == 0);

ENVOY_CONN_LOG(trace, "parsed {} bytes", connection_, total_parsed);
data.drain(total_parsed);
Expand Down Expand Up @@ -618,9 +624,31 @@ int ConnectionImpl::onHeadersCompleteBase() {
return handling_upgrade_ ? 2 : rc;
}

void ConnectionImpl::bufferBody(const char* data, size_t length) {
buffered_body_.add(data, length);
}

void ConnectionImpl::dispatchBufferedBody() {
ASSERT(HTTP_PARSER_ERRNO(&parser_) == HPE_OK || HTTP_PARSER_ERRNO(&parser_) == HPE_PAUSED);
if (buffered_body_.length() > 0) {
onBody(buffered_body_);
buffered_body_.drain(buffered_body_.length());
}
}

void ConnectionImpl::onChunkHeader(bool is_final_chunk) {
if (is_final_chunk) {
// Dispatch body before parsing trailers, so body ends up dispatched even if an error is found
// while processing trailers.
dispatchBufferedBody();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lazy ask: we have a test for this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Http1ServerConnectionImplTest.Http11InvalidTrailerPost

I'm not 100% sure if this is actually necessary. If we find a trailer error, an exception is throw even if trailer proxying is disabled. Pushing the body or throwing it away when trailer processing fails seems about equally good. Thoughts?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mainly I wanted to make sure we regression tested that all body was sent before trailers in the happy path. I don't have strong opinions about making sure all data gets processed in case of errors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I enhanced Http1ServerConnectionImplTest.RequestWithTrailersKept to verify that body is delivered before trailers.

The call to dispatchBufferedBody() in ConnectionImpl::onMessageCompleteBase is enough to accomplish that.

}
}

void ConnectionImpl::onMessageCompleteBase() {
ENVOY_CONN_LOG(trace, "message complete", connection_);

dispatchBufferedBody();

if (handling_upgrade_) {
// If this is an upgrade request, swallow the onMessageComplete. The
// upgrade payload will be treated as stream body.
Expand Down Expand Up @@ -809,12 +837,11 @@ void ServerConnectionImpl::onUrl(const char* data, size_t length) {
}
}

void ServerConnectionImpl::onBody(const char* data, size_t length) {
void ServerConnectionImpl::onBody(Buffer::Instance& data) {
ASSERT(!deferred_end_stream_headers_);
if (active_request_.has_value()) {
ENVOY_CONN_LOG(trace, "body size={}", connection_, length);
Buffer::OwnedImpl buffer(data, length);
active_request_.value().request_decoder_->decodeData(buffer, false);
ENVOY_CONN_LOG(trace, "body size={}", connection_, data.length());
active_request_.value().request_decoder_->decodeData(data, false);
}
}

Expand Down Expand Up @@ -947,12 +974,10 @@ int ClientConnectionImpl::onHeadersComplete() {
return cannotHaveBody() ? 1 : 0;
}

void ClientConnectionImpl::onBody(const char* data, size_t length) {
void ClientConnectionImpl::onBody(Buffer::Instance& data) {
ASSERT(!deferred_end_stream_headers_);
if (pending_response_.has_value()) {
Buffer::OwnedImpl buffer;
buffer.add(data, length);
pending_response_.value().decoder_->decodeData(buffer, false);
pending_response_.value().decoder_->decodeData(data, false);
}
}

Expand Down
29 changes: 23 additions & 6 deletions source/common/http/http1/codec_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,18 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable<Log
*/
size_t dispatchSlice(const char* slice, size_t len);

/**
* Called by the http_parser when body data is received.
* @param data supplies the start address.
* @param length supplies the length.
*/
void bufferBody(const char* data, size_t length);

/**
* Push the accumulated body through the filter pipeline.
*/
void dispatchBufferedBody();

/**
* Called when a request/response is beginning. A base routine happens first then a virtual
* dispatch is invoked.
Expand Down Expand Up @@ -281,18 +293,22 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable<Log
virtual int onHeadersComplete() PURE;

/**
* Called when body data is received.
* @param data supplies the start address.
* @param length supplies the length.
* Called when body data is available for processing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about some comments here or on our buffered data when we buffer and when we process, and why we do it this way?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* @param data supplies the body data
*/
virtual void onBody(const char* data, size_t length) PURE;
virtual void onBody(Buffer::Instance& data) PURE;

/**
* Called when the request/response is complete.
*/
void onMessageCompleteBase();
virtual void onMessageComplete() PURE;

/**
* Called when accepting a chunk header.
*/
void onChunkHeader(bool is_final_chunk);

/**
* @see onResetStreamBase().
*/
Expand Down Expand Up @@ -320,6 +336,7 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable<Log
HeaderParsingState header_parsing_state_{HeaderParsingState::Field};
HeaderString current_header_field_;
HeaderString current_header_value_;
Buffer::OwnedImpl buffered_body_;
Buffer::WatermarkBuffer output_buffer_;
Protocol protocol_{Protocol::Http11};
const uint32_t max_headers_kb_;
Expand Down Expand Up @@ -368,7 +385,7 @@ class ServerConnectionImpl : public ServerConnection, public ConnectionImpl {
void onMessageBegin() override;
void onUrl(const char* data, size_t length) override;
int onHeadersComplete() override;
void onBody(const char* data, size_t length) override;
void onBody(Buffer::Instance& data) override;
void onMessageComplete() override;
void onResetStream(StreamResetReason reason) override;
void sendProtocolError(absl::string_view details) override;
Expand Down Expand Up @@ -447,7 +464,7 @@ class ClientConnectionImpl : public ClientConnection, public ConnectionImpl {
void onMessageBegin() override {}
void onUrl(const char*, size_t) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; }
int onHeadersComplete() override;
void onBody(const char* data, size_t length) override;
void onBody(Buffer::Instance& data) override;
void onMessageComplete() override;
void onResetStream(StreamResetReason reason) override;
void sendProtocolError(absl::string_view details) override;
Expand Down
9 changes: 5 additions & 4 deletions test/benchmark/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ envoy_cc_test_library(
"abseil_symbolize",
"benchmark",
],
deps = select({
"//bazel:disable_signal_trace": [],
"//conditions:default": ["//source/common/signal:sigaction_lib"],
}),
deps = ["//test/test_common:environment_lib"] +
select({
"//bazel:disable_signal_trace": [],
"//conditions:default": ["//source/common/signal:sigaction_lib"],
}),
)
3 changes: 3 additions & 0 deletions test/benchmark/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include "common/signal/signal_action.h"
#endif

#include "test/test_common/environment.h"

#include "absl/debugging/symbolize.h"

// Boilerplate main(), which discovers benchmarks and runs them.
Expand All @@ -23,5 +25,6 @@ int main(int argc, char** argv) {
if (benchmark::ReportUnrecognizedArguments(argc, argv)) {
return 1;
}
Envoy::TestEnvironment::initializeOptions(argc, argv);
benchmark::RunSpecifiedBenchmarks();
}
Loading