Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
2 changes: 1 addition & 1 deletion source/common/http/conn_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1388,7 +1388,7 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapSharedPt

filter_manager_.streamInfo().setRequestHeaders(*request_headers_);

const bool upgrade_rejected = filter_manager_.createFilterChain() == false;
const bool upgrade_rejected = filter_manager_.createDownstreamFilterChain();

if (connection_manager_.config_->flushAccessLogOnNewRequest()) {
log(AccessLog::AccessLogType::DownstreamStart);
Expand Down
80 changes: 55 additions & 25 deletions source/common/http/filter_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1036,7 +1036,7 @@ void DownstreamFilterManager::prepareLocalReplyViaFilterChain(
// For early error handling, do a best-effort attempt to create a filter chain
// to ensure access logging. If the filter chain already exists this will be
// a no-op.
createFilterChain();
createDownstreamFilterChain();

if (prepared_local_reply_) {
return;
Expand Down Expand Up @@ -1077,6 +1077,11 @@ void DownstreamFilterManager::executeLocalReplyIfPrepared() {
Utility::encodeLocalReply(state_.destroyed_, std::move(prepared_local_reply_));
}

bool DownstreamFilterManager::createDownstreamFilterChain() {
return createFilterChain(filter_chain_factory_, /*allow_upgrade_filter_chain=*/true, false)
.upgrade_rejected;
}

void DownstreamFilterManager::sendLocalReplyViaFilterChain(
bool is_grpc_request, Code code, absl::string_view body,
const std::function<void(ResponseHeaderMap& headers)>& modify_headers, bool is_head_request,
Expand All @@ -1086,7 +1091,7 @@ void DownstreamFilterManager::sendLocalReplyViaFilterChain(
// For early error handling, do a best-effort attempt to create a filter chain
// to ensure access logging. If the filter chain already exists this will be
// a no-op.
createFilterChain();
createDownstreamFilterChain();

Utility::sendLocalReply(
state_.destroyed_,
Expand Down Expand Up @@ -1623,11 +1628,9 @@ void FilterManager::contextOnContinue(ScopeTrackedObjectStack& tracked_object_st
tracked_object_stack.add(filter_manager_callbacks_.scope());
}

bool FilterManager::createFilterChain() {
if (state_.created_filter_chain_) {
return false;
}
bool upgrade_rejected = false;
FilterManager::CreateFilterChainResult

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.

why change this one to CreateFilterChainResult? If it creates isn't the upgrade automatically not rejected? Seems simpler as a boolean

@wbpcode wbpcode Nov 15, 2024

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

But the created may be true when the upgrade is rejected. And the created status is necessary by the upstream filter chain creation.

#37150 will make thing become more complex. So, I think I have to make some changes here to make it's easy to work with #37150

FilterManager::createUpgradeFilterChain(const FilterChainFactory& filter_chain_factory,
const FilterChainOptionsImpl& options) {
const HeaderEntry* upgrade = nullptr;
if (filter_manager_callbacks_.requestHeaders()) {
upgrade = filter_manager_callbacks_.requestHeaders()->Upgrade();
Expand All @@ -1638,28 +1641,55 @@ bool FilterManager::createFilterChain() {
}
}

if (upgrade == nullptr) {
// No upgrade header, no upgrade filter chain.
return {false, false};
}

const Router::RouteEntry::UpgradeMap* upgrade_map = filter_manager_callbacks_.upgradeMap();
if (filter_chain_factory.createUpgradeFilterChain(upgrade->value().getStringView(), upgrade_map,
*this, options)) {
filter_manager_callbacks_.upgradeFilterChainCreated();
// The upgrade filter chain is created successfully.
return {true, false};
}

// The upgrade filter chain is rejected. Fall through to the default filter chain.
// The default filter chain will be used to handle the upgrade failure local reply.
return {false, true};
}

FilterManager::CreateFilterChainResult
FilterManager::createFilterChain(const FilterChainFactory& filter_chain_factory,
bool allow_upgrade_filter_chain, bool only_create_if_configured) {
if (state_.created_filter_chain_) {
return {true, false};

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.

is this necessarily correct? Doesn't it depend on what happened previously? maybe at least comment if this is preserving legacy behavior

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, this is mainly for preserving legacy behavior. But I think you are right because the created_filter_chain_ flag only ensure we have created filter chain. Set the upgrade_rejected to false may be wrong. We may need other way to expose the upgrade failure.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I found we have added a upgradeFilterChainCreated callback to expose/tell the success status of upgrade filter chain creation. This callabck typically could be reused to expose the failure status of upgrade filter chain creation. Then the createFilterChain() could only return a bool to tell if the filter chain is created or not.

}

// This filter chain options is only used for the downstream HTTP filter chains for now. So, try
// to set valid initial route only when the downstream callbacks is available.
FilterChainOptionsImpl options(
filter_manager_callbacks_.downstreamCallbacks().has_value() ? streamInfo().route() : nullptr);

state_.created_filter_chain_ = true;
if (upgrade != nullptr) {
const Router::RouteEntry::UpgradeMap* upgrade_map = filter_manager_callbacks_.upgradeMap();
bool upgrade_rejected = false;

if (filter_chain_factory_.createUpgradeFilterChain(upgrade->value().getStringView(),
upgrade_map, *this, options)) {
filter_manager_callbacks_.upgradeFilterChainCreated();
return true;
} else {
upgrade_rejected = true;
// Fall through to the default filter chain. The function calling this
// will send a local reply indicating that the upgrade failed.
if (allow_upgrade_filter_chain) {
const auto upgrade_result = createUpgradeFilterChain(filter_chain_factory, options);
if (upgrade_result.created) {
ASSERT(!upgrade_result.upgrade_rejected);
state_.created_filter_chain_ = true;
return upgrade_result;
}
// The upgrade filter chain may be unnecessary or be rejected. Fall through to the default
// filter chain anyway.
upgrade_rejected = upgrade_result.upgrade_rejected;
}

filter_chain_factory_.createFilterChain(*this, false, options);
return !upgrade_rejected;
const bool created =
filter_chain_factory.createFilterChain(*this, only_create_if_configured, options);
state_.created_filter_chain_ = created;

return {created, upgrade_rejected};
}

void ActiveStreamDecoderFilter::requestDataDrained() {
Expand Down Expand Up @@ -1714,11 +1744,11 @@ bool ActiveStreamDecoderFilter::recreateStream(const ResponseHeaderMap* headers)

if (headers != nullptr) {
// The call to setResponseHeaders is needed to ensure that the headers are properly logged in
// access logs before the stream is destroyed. Since the function expects a ResponseHeaderPtr&&,
// ownership of the headers must be passed. This cannot happen earlier in the flow (such as in
// the call to setupRedirect) because at that point it is still possible for the headers to be
// used in a different logical branch. We work around this by creating a copy and passing
// ownership of the copy instead.
// access logs before the stream is destroyed. Since the function expects a
// ResponseHeaderPtr&&, ownership of the headers must be passed. This cannot happen earlier in
Comment thread
wbpcode marked this conversation as resolved.
// the flow (such as in the call to setupRedirect) because at that point it is still possible
// for the headers to be used in a different logical branch. We work around this by creating a
// copy and passing ownership of the copy instead.
ResponseHeaderMapPtr headers_copy = createHeaderMap<ResponseHeaderMapImpl>(*headers);
parent_.filter_manager_callbacks_.setResponseHeaders(std::move(headers_copy));
parent_.filter_manager_callbacks_.chargeStats(*headers);
Expand Down
35 changes: 27 additions & 8 deletions source/common/http/filter_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -639,11 +639,10 @@ class FilterManager : public ScopeTrackedObject,
FilterManager(FilterManagerCallbacks& filter_manager_callbacks, Event::Dispatcher& dispatcher,
OptRef<const Network::Connection> connection, uint64_t stream_id,
Buffer::BufferMemoryAccountSharedPtr account, bool proxy_100_continue,
uint32_t buffer_limit, const FilterChainFactory& filter_chain_factory)
uint32_t buffer_limit)
: filter_manager_callbacks_(filter_manager_callbacks), dispatcher_(dispatcher),
connection_(connection), stream_id_(stream_id), account_(std::move(account)),
proxy_100_continue_(proxy_100_continue), buffer_limit_(buffer_limit),
filter_chain_factory_(filter_chain_factory) {}
proxy_100_continue_(proxy_100_continue), buffer_limit_(buffer_limit) {}

~FilterManager() override {
ASSERT(state_.destroyed_);
Expand Down Expand Up @@ -837,8 +836,23 @@ class FilterManager : public ScopeTrackedObject,
virtual StreamInfo::StreamInfo& streamInfo() PURE;
virtual const StreamInfo::StreamInfo& streamInfo() const PURE;

// Set up the Encoder/Decoder filter chain.
bool createFilterChain();
struct CreateFilterChainResult {
bool created{};
bool upgrade_rejected{};
};

/**
* Set up the Encoder/Decoder filter chain.
* @param filter_chain_factory the factory to create the filter chain.
* @param allow_upgrade_filter_chain whether to allow the creation of an upgrade filter chain.
* This only should be set to true for downstream HTTP filter chain.
* @param only_create_if_configured whether to only create the filter chain if it is configured
* explicitly. This only makes sense for upstream HTTP filter chain.

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.

ooc is it ever not true for upstream filter chain? I'm wondering if we can infer this as we do the upgrade allowed option if they're just the inverse of each other

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ooc is it ever not true for upstream filter chain?

Yeah...it will be false when we try to create filters from router and cluster at second time. As I said before, i think we should remove this by providing a default filter chain factory for upstream filter chain.

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.

Would it be worth doing that now? default filter chain is just codec filter right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Would it be worth doing that now? default filter chain is just codec filter right?

I think it deserves. It not just make code simple also reduce memory overhead of cluster because we now need keep a default filter chain for every cluster.

*
*/
CreateFilterChainResult createFilterChain(const FilterChainFactory& filter_chain_factory,

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.

Can you comment a bit more about the motivation for this refactor?
Having a single createFilterChain function where one argument only works for downstream and one for upstream feels ugly. Why not retain createDownstream/createUpstream and only pass the relevant args?
/wait-any

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Can you comment a bit more about the motivation for this refactor?
/wait-any

The initial motivation is I wanna to refactor the http filter chain to use vector rather than list as the container of filters because the filter chain needn't O(1) modification anyway. And rather than ActiveStremDecoderFilterPtr, I want to use ActiveStremDecoderFilter directly. This could reduce unnecessary heap allocations.

This refactoring requires us to set filter callbacks at the end of filter chain creation. So I need to ensure the filter chain creation is under the control of FilterManager.

And even not for that, make sure all filter chain creation logic in same way is better choice.

Having a single createFilterChain function where one argument only works for downstream and one for upstream feels ugly. Why not retain createDownstream/createUpstream and only pass the relevant args?

Yeah, I can make the create filter chain a shared private method and create a wrapper method for upstream filter chain creation.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I found I acutally needn't the upgrade option, because we can check whether the downstream stream callbacks is valid or not to determine to try upgrade or not.
Now, I removed it, code is cleaner now.

@wbpcode wbpcode Nov 14, 2024

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

By the way, after a check to the upstream filter chain creation, I think even only_create_if_configured is unnecessary. Rather add some specific logic/parameter to the FilterChainFactory.createFilterChain, it would be better to use a default FilterChainFactory instance. When no valid filters are provided by both router and cluster, then we can construct a default FilterChainFactory to create the filter chain. (Only contains UpstreamCodecFilter)

This way should be much cleaner.

(If it's ok to you, I can also do a minor refactoring to this with a new PR).

bool allow_upgrade_filter_chain,
bool only_create_if_configured);

OptRef<const Network::Connection> connection() const { return connection_; }

Expand Down Expand Up @@ -966,6 +980,9 @@ class FilterManager : public ScopeTrackedObject,
// Indicates which filter to start the iteration with.
enum class FilterIterationStartState { AlwaysStartFromNext, CanStartFromCurrent };

CreateFilterChainResult createUpgradeFilterChain(const FilterChainFactory& filter_chain_factory,
const FilterChainOptionsImpl& options);

// Returns the encoder filter to start iteration with.
std::list<ActiveStreamEncoderFilterPtr>::iterator
commonEncodePrefix(ActiveStreamEncoderFilter* filter, bool end_stream,
Expand Down Expand Up @@ -1053,7 +1070,6 @@ class FilterManager : public ScopeTrackedObject,
std::make_shared<Network::Socket::Options>();
absl::optional<Upstream::LoadBalancerContext::OverrideHost> upstream_override_host_;

const FilterChainFactory& filter_chain_factory_;
// TODO(snowp): Once FM has been moved to its own file we'll make these private classes of FM,
// at which point they no longer need to be friends.
friend ActiveStreamFilterBase;
Expand Down Expand Up @@ -1108,11 +1124,11 @@ class DownstreamFilterManager : public FilterManager {
StreamInfo::FilterStateSharedPtr parent_filter_state,
Server::OverloadManager& overload_manager)
: FilterManager(filter_manager_callbacks, dispatcher, connection, stream_id, account,
proxy_100_continue, buffer_limit, filter_chain_factory),
proxy_100_continue, buffer_limit),
stream_info_(protocol, time_source, connection.connectionInfoProviderSharedPtr(),
StreamInfo::FilterState::LifeSpan::FilterChain,
std::move(parent_filter_state)),
local_reply_(local_reply),
local_reply_(local_reply), filter_chain_factory_(filter_chain_factory),
downstream_filter_load_shed_point_(overload_manager.getLoadShedPoint(
Server::LoadShedPointName::get().HttpDownstreamFilterCheck)),
use_filter_manager_state_for_downstream_end_stream_(Runtime::runtimeFeatureEnabled(
Expand All @@ -1136,6 +1152,8 @@ class DownstreamFilterManager : public FilterManager {
stream_info_.setDownstreamRemoteAddress(downstream_remote_address);
}

bool createDownstreamFilterChain();

/**
* Called before local reply is made by the filter manager.
* @param data the data associated with the local reply.
Expand Down Expand Up @@ -1212,6 +1230,7 @@ class DownstreamFilterManager : public FilterManager {
private:
OverridableRemoteConnectionInfoSetterStreamInfo stream_info_;
const LocalReply::LocalReply& local_reply_;
const FilterChainFactory& filter_chain_factory_;
Utility::PreparedLocalReplyPtr prepared_local_reply_{nullptr};
Server::LoadShedPoint* downstream_filter_load_shed_point_{nullptr};
// Set by the envoy.reloadable_features.use_filter_manager_state_for_downstream_end_stream runtime
Expand Down
2 changes: 1 addition & 1 deletion source/common/router/router.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ class FilterUtility {
/**
* Configuration for the router filter.
*/
class FilterConfig : Http::FilterChainFactory {
class FilterConfig : public Http::FilterChainFactory {
public:
FilterConfig(Server::Configuration::CommonFactoryContext& factory_context,
Stats::StatName stat_prefix, const LocalInfo::LocalInfo& local_info,
Expand Down
18 changes: 9 additions & 9 deletions source/common/router/upstream_request.cc
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,9 @@ class UpstreamFilterManager : public Http::FilterManager {
UpstreamFilterManager(Http::FilterManagerCallbacks& filter_manager_callbacks,
Event::Dispatcher& dispatcher, OptRef<const Network::Connection> connection,
uint64_t stream_id, Buffer::BufferMemoryAccountSharedPtr account,
bool proxy_100_continue, uint32_t buffer_limit,
const Http::FilterChainFactory& filter_chain_factory,
UpstreamRequest& request)
bool proxy_100_continue, uint32_t buffer_limit, UpstreamRequest& request)
: FilterManager(filter_manager_callbacks, dispatcher, connection, stream_id, account,
proxy_100_continue, buffer_limit, filter_chain_factory),
proxy_100_continue, buffer_limit),
upstream_request_(request) {}

StreamInfo::StreamInfo& streamInfo() override {
Expand Down Expand Up @@ -142,18 +140,20 @@ UpstreamRequest::UpstreamRequest(RouterFilterInterface& parent,
filter_manager_ = std::make_unique<UpstreamFilterManager>(
*filter_manager_callbacks_, parent_.callbacks()->dispatcher(), UpstreamRequest::connection(),
parent_.callbacks()->streamId(), parent_.callbacks()->account(), true,
parent_.callbacks()->decoderBufferLimit(), *parent_.cluster(), *this);
parent_.callbacks()->decoderBufferLimit(), *this);
// Attempt to create custom cluster-specified filter chain
bool created = parent_.cluster()->createFilterChain(*filter_manager_,
/*only_create_if_configured=*/true);
bool created = filter_manager_
->createFilterChain(*parent_.cluster(), false,
/*only_create_if_configured=*/true)
.created;
if (!created) {
// Attempt to create custom router-specified filter chain.
created = parent_.config().createFilterChain(*filter_manager_);
created = filter_manager_->createFilterChain(parent_.config(), false, false).created;
}
if (!created) {
// Neither cluster nor router have a custom filter chain; add the default
// cluster filter chain, which only consists of the codec filter.
created = parent_.cluster()->createFilterChain(*filter_manager_, false);
created = filter_manager_->createFilterChain(*parent_.cluster(), false, false).created;
}
// There will always be a codec filter present, which sets the upstream
// interface. Fast-fail any tests that don't set up mocks correctly.
Expand Down
Loading