-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
http: fixing connection close behavior #10957
Changes from 7 commits
1e018fd
caf7388
b2e48bf
45c9076
d6e73ad
f502fe5
a9b9347
ac320b1
632b175
c705a09
8e1d417
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,6 +38,7 @@ | |
#include "common/http/utility.h" | ||
#include "common/network/utility.h" | ||
#include "common/router/config_impl.h" | ||
#include "common/runtime/runtime_features.h" | ||
#include "common/runtime/runtime_impl.h" | ||
#include "common/stats/timespan_impl.h" | ||
|
||
|
@@ -770,15 +771,26 @@ const Network::Connection* ConnectionManagerImpl::ActiveStream::connection() { | |
// can't route select properly without full headers), checking state required to | ||
// serve error responses (connection close, head requests, etc), and | ||
// modifications which may themselves affect route selection. | ||
// | ||
// TODO(alyssawilk) all the calls here should be audited for order priority, | ||
// e.g. many early returns do not currently handle connection: close properly. | ||
void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapPtr&& headers, | ||
bool end_stream) { | ||
ScopeTrackerScopeState scope(this, | ||
connection_manager_.read_callbacks_->connection().dispatcher()); | ||
request_headers_ = std::move(headers); | ||
|
||
// Both saw_connection_close_ and is_head_request_ affect local replies: set | ||
// them as early as possible. | ||
Protocol protocol = connection_manager_.codec_->protocol(); | ||
bool fixed_connection_close = | ||
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. nit: const both local vars |
||
Runtime::runtimeFeatureEnabled("envoy.reloadable_features.fixed_connection_close"); | ||
if (fixed_connection_close) { | ||
state_.saw_connection_close_ = | ||
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. nit: s/saw_connection_close_/should_close_connection_ or something since I think the meaning of this over time is now different? |
||
HeaderUtility::shouldCloseConnection(protocol, *request_headers_); | ||
} | ||
if (Http::Headers::get().MethodValues.Head == | ||
request_headers_->Method()->value().getStringView()) { | ||
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. (Moved code) This has come up in other reviews but I think we are being inconsistent about whether we check Method for null or not. I'm fine either way but I think we should be consistent about this and/or have an ASSERT? |
||
state_.is_head_request_ = true; | ||
} | ||
|
||
// TODO(alyssawilk) remove this synthetic path in a follow-up PR, including | ||
// auditing of empty path headers. We check for path because HTTP/2 connect requests may have a | ||
// path. | ||
|
@@ -799,10 +811,6 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapPtr&& he | |
snapped_route_config_ = connection_manager_.config_.routeConfigProvider()->config(); | ||
} | ||
|
||
if (Http::Headers::get().MethodValues.Head == | ||
request_headers_->Method()->value().getStringView()) { | ||
state_.is_head_request_ = true; | ||
} | ||
ENVOY_STREAM_LOG(debug, "request headers complete (end_stream={}):\n{}", *this, end_stream, | ||
*request_headers_); | ||
|
||
|
@@ -840,7 +848,6 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapPtr&& he | |
connection_manager_.stats_.scope_); | ||
|
||
// Make sure we are getting a codec version we support. | ||
Protocol protocol = connection_manager_.codec_->protocol(); | ||
if (protocol == Protocol::Http10) { | ||
// Assume this is HTTP/1.0. This is fine for HTTP/0.9 but this code will also affect any | ||
// requests with non-standard version numbers (0.9, 1.3), basically anything which is not | ||
|
@@ -853,7 +860,7 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapPtr&& he | |
sendLocalReply(false, Code::UpgradeRequired, "", nullptr, state_.is_head_request_, | ||
absl::nullopt, StreamInfo::ResponseCodeDetails::get().LowVersion); | ||
return; | ||
} else { | ||
} else if (!fixed_connection_close) { | ||
// HTTP/1.0 defaults to single-use connections. Make sure the connection | ||
// will be closed unless Keep-Alive is present. | ||
state_.saw_connection_close_ = true; | ||
|
@@ -863,23 +870,22 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapPtr&& he | |
state_.saw_connection_close_ = false; | ||
} | ||
} | ||
} | ||
|
||
if (!request_headers_->Host()) { | ||
if ((protocol == Protocol::Http10) && | ||
if (!request_headers_->Host() && | ||
!connection_manager_.config_.http1Settings().default_host_for_http_10_.empty()) { | ||
// Add a default host if configured to do so. | ||
request_headers_->setHost( | ||
connection_manager_.config_.http1Settings().default_host_for_http_10_); | ||
} else { | ||
// Require host header. For HTTP/1.1 Host has already been translated to :authority. | ||
sendLocalReply(Grpc::Common::hasGrpcContentType(*request_headers_), Code::BadRequest, "", | ||
nullptr, state_.is_head_request_, absl::nullopt, | ||
StreamInfo::ResponseCodeDetails::get().MissingHost); | ||
return; | ||
} | ||
} | ||
|
||
if (!request_headers_->Host()) { | ||
// Require host header. For HTTP/1.1 Host has already been translated to :authority. | ||
sendLocalReply(Grpc::Common::hasGrpcContentType(*request_headers_), Code::BadRequest, "", | ||
nullptr, state_.is_head_request_, absl::nullopt, | ||
StreamInfo::ResponseCodeDetails::get().MissingHost); | ||
return; | ||
} | ||
|
||
// Verify header sanity checks which should have been performed by the codec. | ||
ASSERT(HeaderUtility::requestHeadersValid(*request_headers_).has_value() == false); | ||
|
||
|
@@ -909,15 +915,15 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapPtr&& he | |
return; | ||
} | ||
|
||
if (protocol == Protocol::Http11 && request_headers_->Connection() && | ||
if (!fixed_connection_close && protocol == Protocol::Http11 && request_headers_->Connection() && | ||
absl::EqualsIgnoreCase(request_headers_->Connection()->value().getStringView(), | ||
Http::Headers::get().ConnectionValues.Close)) { | ||
state_.saw_connection_close_ = true; | ||
} | ||
// Note: Proxy-Connection is not a standard header, but is supported here | ||
// since it is supported by http-parser the underlying parser for http | ||
// requests. | ||
if (protocol < Protocol::Http2 && !state_.saw_connection_close_ && | ||
if (!fixed_connection_close && protocol < Protocol::Http2 && !state_.saw_connection_close_ && | ||
request_headers_->ProxyConnection() && | ||
absl::EqualsIgnoreCase(request_headers_->ProxyConnection()->value().getStringView(), | ||
Http::Headers::get().ConnectionValues.Close)) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -190,5 +190,33 @@ HeaderUtility::requestHeadersValid(const RequestHeaderMap& headers) { | |
return absl::nullopt; | ||
} | ||
|
||
bool HeaderUtility::shouldCloseConnection(Http::Protocol protocol, | ||
mattklein123 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const RequestHeaderMap& request_headers) { | ||
// HTTP/1.0 defaults to single-use connections. Make sure the connection will be closed unless | ||
// Keep-Alive is present. | ||
if (protocol == Protocol::Http10 && | ||
(!request_headers.Connection() || | ||
!Envoy::StringUtil::caseFindToken(request_headers.Connection()->value().getStringView(), ",", | ||
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. Hmm, the standard is a bit fuzzy, but it does not seem to say that hop-by-hop headers must be listed in the Connection header. So is it possible that Keep-Alive header is present but not listed in Connection? Do we even care about this case? 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. AFIK the tokens keep-alive and close are special, and can be present in Connection without indicating a hop by hop header. I don't think I've ever sent keep-alive sent as a header, so I'm inclined to let this stand unless someone sends a feature request :-P |
||
Http::Headers::get().ConnectionValues.KeepAlive))) { | ||
return true; | ||
} | ||
|
||
if (protocol == Protocol::Http11 && request_headers.Connection() && | ||
Envoy::StringUtil::caseFindToken(request_headers.Connection()->value().getStringView(), ",", | ||
Http::Headers::get().ConnectionValues.Close)) { | ||
return true; | ||
} | ||
|
||
// Note: Proxy-Connection is not a standard header, but is supported here | ||
// since it is supported by http-parser the underlying parser for http | ||
// requests. | ||
if (protocol < Protocol::Http2 && request_headers.ProxyConnection() && | ||
Envoy::StringUtil::caseFindToken(request_headers.ProxyConnection()->value().getStringView(), | ||
",", Http::Headers::get().ConnectionValues.Close)) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
} // namespace Http | ||
} // namespace Envoy |
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.
The behavior seems to have only changes for H/1.0. Would it be easier to move the runtime flag check into the shouldCloseConnection() and then get rid of the
if (!fixed_connection_close ...)
cases below?