Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,8 @@ message ResponseMapPerRoute {
// Disable the response map filter for this particular vhost or route.
// If disabled is specified in multiple per-filter-configs, the most specific one will be used.
bool disabled = 1 [(validate.rules).bool = {const: true}];

// Override the global configuration of the response map filter with this new config.
ResponseMap response_map = 2 [(validate.rules).message = {required: true}];
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 12 additions & 12 deletions source/common/response_map/response_map.cc
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ using BodyFormatterPtr = std::unique_ptr<BodyFormatter>;
class ResponseMapper {
public:
ResponseMapper(
const envoy::extensions::filters::http::response_map::v3::ResponseMapper&
config,
Server::Configuration::FactoryContext& context)
const envoy::extensions::filters::http::response_map::v3::ResponseMapper& config,
Server::Configuration::CommonFactoryContext& context,
ProtobufMessage::ValidationVisitor& validationVisitor)
: filter_(AccessLog::FilterFactory::fromProto(config.filter(), context.runtime(),
context.random(),
context.messageValidationVisitor())) {
validationVisitor)) {
if (config.has_status_code()) {
status_code_ = static_cast<Http::Code>(config.status_code().value());
}
Expand Down Expand Up @@ -134,14 +134,14 @@ class ResponseMapImpl : public ResponseMap {
ResponseMapImpl() : body_formatter_(std::make_unique<BodyFormatter>()) {}

ResponseMapImpl(
const envoy::extensions::filters::http::response_map::v3::ResponseMap&
config,
Server::Configuration::FactoryContext& context)
const envoy::extensions::filters::http::response_map::v3::ResponseMap& config,
Server::Configuration::CommonFactoryContext& context,
ProtobufMessage::ValidationVisitor& validationVisitor)
: body_formatter_(config.has_body_format()
? std::make_unique<BodyFormatter>(config.body_format(), context.api())
: std::make_unique<BodyFormatter>()) {
for (const auto& mapper : config.mappers()) {
mappers_.emplace_back(std::make_unique<ResponseMapper>(mapper, context));
mappers_.emplace_back(std::make_unique<ResponseMapper>(mapper, context, validationVisitor));
}
}

Expand Down Expand Up @@ -195,10 +195,10 @@ class ResponseMapImpl : public ResponseMap {
ResponseMapPtr Factory::createDefault() { return std::make_unique<ResponseMapImpl>(); }

ResponseMapPtr Factory::create(
const envoy::extensions::filters::http::response_map::v3::ResponseMap&
config,
Server::Configuration::FactoryContext& context) {
return std::make_unique<ResponseMapImpl>(config, context);
const envoy::extensions::filters::http::response_map::v3::ResponseMap& config,
Server::Configuration::CommonFactoryContext& context,
ProtobufMessage::ValidationVisitor& validationVisitor) {
return std::make_unique<ResponseMapImpl>(config, context, validationVisitor);
}

} // namespace ResponseMap
Expand Down
8 changes: 4 additions & 4 deletions source/common/response_map/response_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ class Factory {
* Create a ResponseMap object from ProtoConfig
*/
static ResponseMapPtr
create(const envoy::extensions::filters::http::response_map::v3::ResponseMap&
config,
Server::Configuration::FactoryContext& context);
create(const envoy::extensions::filters::http::response_map::v3::ResponseMap& config,
Server::Configuration::CommonFactoryContext& context,
ProtobufMessage::ValidationVisitor& validationVisitor);

/**
* Create a default ResponseMap object with empty config.
* It is used at places without Server::Configuration::FactoryContext.
* It is used at places without Server::Configuration::CommonFactoryContext.
*/
static ResponseMapPtr createDefault();
};
Expand Down
6 changes: 3 additions & 3 deletions source/extensions/filters/http/response_map/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ Http::FilterFactoryCb ResponseMapFilterFactory::createFilterFactoryFromProtoType
Router::RouteSpecificFilterConfigConstSharedPtr
ResponseMapFilterFactory::createRouteSpecificFilterConfigTyped(
const envoy::extensions::filters::http::response_map::v3::ResponseMapPerRoute& proto_config,
Server::Configuration::ServerFactoryContext&,
ProtobufMessage::ValidationVisitor&) {
return std::make_shared<FilterConfigPerRoute>(proto_config);
Server::Configuration::ServerFactoryContext& context,
ProtobufMessage::ValidationVisitor& validationVisitor) {
return std::make_shared<FilterConfigPerRoute>(proto_config, context, validationVisitor);
}

/**
Expand Down
66 changes: 52 additions & 14 deletions source/extensions/filters/http/response_map/response_map_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,61 @@ ResponseMapFilterConfig::ResponseMapFilterConfig(
const envoy::extensions::filters::http::response_map::v3::ResponseMap& proto_config,
const std::string&,
Server::Configuration::FactoryContext& context)
: response_map_(ResponseMap::Factory::create(proto_config, context)) {}
: response_map_(ResponseMap::Factory::create(proto_config, context, context.messageValidationVisitor())) {}

FilterConfigPerRoute::FilterConfigPerRoute(
const envoy::extensions::filters::http::response_map::v3::ResponseMapPerRoute& proto_config,
Server::Configuration::ServerFactoryContext& context,
ProtobufMessage::ValidationVisitor& validationVisitor)
: disabled_(proto_config.disabled()),
response_map_(proto_config.has_response_map() ?
ResponseMap::Factory::create(proto_config.response_map(), context, validationVisitor) :
nullptr) {}

ResponseMapFilter::ResponseMapFilter(ResponseMapFilterConfigSharedPtr config)
: config_(config) {}

const FilterConfigPerRoute*
ResponseMapFilter::getRouteSpecificConfig(void) {
const auto& route = decoder_callbacks_->route();
if (route == nullptr) {
return nullptr;
}

const auto* per_route_config =
Http::Utility::resolveMostSpecificPerFilterConfig<FilterConfigPerRoute>(
Extensions::HttpFilters::HttpFilterNames::get().ResponseMap, route);
ENVOY_LOG(trace, "response map filter: found route. has per_route_config? {}",
per_route_config != nullptr);

return per_route_config;
}

Http::FilterHeadersStatus ResponseMapFilter::decodeHeaders(Http::RequestHeaderMap& request_headers,
bool end_stream) {
ENVOY_LOG(trace, "response map filter: decodeHeaders with end_stream = {}", end_stream);

// Disable filter per route config if applies
if (decoder_callbacks_->route() != nullptr) {
const auto* per_route_config =
Http::Utility::resolveMostSpecificPerFilterConfig<FilterConfigPerRoute>(
Extensions::HttpFilters::HttpFilterNames::get().ResponseMap,
decoder_callbacks_->route());
ENVOY_LOG(trace, "response map filter: found route. has per_route_config? {}",
per_route_config != nullptr);
if (per_route_config != nullptr && per_route_config->disabled()) {
response_map_ = config_->response_map();
request_headers_ = &request_headers;

// Find per-route config if it exists.
const FilterConfigPerRoute* per_route_config = getRouteSpecificConfig();
if (per_route_config != nullptr) {
// Possibly disable the response map entirely if set in the per-route config.
if (per_route_config->disabled()) {
ENVOY_LOG(trace, "response map filter: disabling due to per_route_config");
disabled_ = true;
return Http::FilterHeadersStatus::Continue;
}

// Use a per-route response_map if it exists.
const ResponseMap::ResponseMapPtr* response_map = per_route_config->response_map();
if (response_map != nullptr) {
ENVOY_LOG(trace, "response map filter: using per_route_config response map");
response_map_ = response_map;
}
}

request_headers_ = &request_headers;
return Http::FilterHeadersStatus::Continue;
}

Expand All @@ -64,9 +94,13 @@ Http::FilterHeadersStatus ResponseMapFilter::encodeHeaders(Http::ResponseHeaderM
// we'll need to set the content-length (and possibly other) headers later.
response_headers_ = &headers;

// The reponse_map_ pointer should have been set in decodeHeaders.
ASSERT(response_map_ != nullptr);
const ResponseMap::ResponseMapPtr& response_map = *response_map_;

// Check whether we should rewrite this response based on response headers.
do_rewrite_ = config_->response_map()->match(
request_headers_, headers, encoder_callbacks_->streamInfo());
do_rewrite_ = response_map->match(request_headers_, headers, encoder_callbacks_->streamInfo());

ENVOY_LOG(trace, "response map filter: do_rewrite_ {}", do_rewrite_);

// If we decided not to rewrite the response, simply pass through to other
Expand Down Expand Up @@ -139,12 +173,16 @@ void ResponseMapFilter::doRewrite(void) {
// if we did see a response, we should have drained any data we saw.
ASSERT(encoding_buffer == nullptr || encoding_buffer->length() == 0);

// The reponse_map_ pointer should have been set in decodeHeaders.
ASSERT(response_map_ != nullptr);
const ResponseMap::ResponseMapPtr& response_map = *response_map_;

// Fill in new_body and new_content_type using the response map rewrite.
// Pass in the request and response headers that we observed on the
// decodeHeaders and encodeHeaders paths, respectively.
std::string new_body;
absl::string_view new_content_type;
config_->response_map()->rewrite(
response_map->rewrite(
request_headers_,
*response_headers_,
encoder_callbacks_->streamInfo(),
Expand Down
62 changes: 56 additions & 6 deletions source/extensions/filters/http/response_map/response_map_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,67 @@ namespace HttpFilters {
namespace ResponseMapFilter {

/**
* Configuration for the HTTP response filter.
* Configuration for the response map filter.
*/
class ResponseMapFilterConfig {
public:
ResponseMapFilterConfig(
const envoy::extensions::filters::http::response_map::v3::ResponseMap& proto_config,
const std::string&, Server::Configuration::FactoryContext& context);
const ResponseMap::ResponseMapPtr& response_map() { return response_map_; }
const ResponseMap::ResponseMapPtr* response_map() const { return &response_map_; }

private:
const ResponseMap::ResponseMapPtr response_map_;
};
using ResponseMapFilterConfigSharedPtr = std::shared_ptr<ResponseMapFilterConfig>;

/*
* Per-route configuration for the response map filter.
* Allows the response map to be overriden or disabled.
*/
class FilterConfigPerRoute : public Router::RouteSpecificFilterConfig {
public:
FilterConfigPerRoute(
const envoy::extensions::filters::http::response_map::v3::ResponseMapPerRoute&
config)
: disabled_(config.disabled()) {}
const envoy::extensions::filters::http::response_map::v3::ResponseMapPerRoute& proto_config,
Server::Configuration::ServerFactoryContext& context,
ProtobufMessage::ValidationVisitor& validationVisitor);
bool disabled() const { return disabled_; }
const ResponseMap::ResponseMapPtr* response_map() const { return &response_map_; }

private:
bool disabled_;
const ResponseMap::ResponseMapPtr response_map_;
};

/**
* The response map filter.
*
* Filters on both the decoding stage (request moving upstream) and the encoding
* stage (response moving downstream).
*
* The request headers are captured in decodeHeaders to be later passed to a response
* map mapper for matching purposes.
*
* The response headers are captured in encodeHeaders to both be inspected by a
* response mapper for matching purposes. At this point, we decide if the response
* body (or, in theory, the headers too) will be rewritten. If the response is
* headers-only, we do the rewrite right away because encodeData won't be called.
*
* Otherwise, we wait until encodeData, drain anything we get from the upstream,
* and finally rewrite the response body.
*
* The response map filter maintains three pieces of state:
*
* disabled: set to true if a per-route config is found in the decode stage and
* the disabled flag is set. this disables rewrite behavior entirely.
*
* do_rewrite: set to true if the chosen response mapper matched, and we should
* eventually do a response body (and/or header) rewrite.
*
* response_map: set to a pointer to the response map that should be used to match
* and rewrite. if a per-route config is found and its mapper is set,
* use that. otherwise, use the globally configured mapper.
*/
class ResponseMapFilter : public Http::StreamFilter, Logger::Loggable<Logger::Id::filter> {
public:
ResponseMapFilter(ResponseMapFilterConfigSharedPtr config);
Expand Down Expand Up @@ -74,6 +109,13 @@ class ResponseMapFilter : public Http::StreamFilter, Logger::Loggable<Logger::Id
encoder_callbacks_ = &callbacks;
};

// Get the route specific config to use for this filter iteration.
const FilterConfigPerRoute* getRouteSpecificConfig(void);

// Do the actual rewrite using the mapper we decided to use.
//
// Cannot be called if the filter was disabled (disabled_ == true) or if we decided
// not to do the rewrite (do_rewrite_ == false). Requires that response_map_ is set.
void doRewrite();

private:
Expand All @@ -82,8 +124,16 @@ class ResponseMapFilter : public Http::StreamFilter, Logger::Loggable<Logger::Id
Http::StreamEncoderFilterCallbacks* encoder_callbacks_{};
Http::ResponseHeaderMap* response_headers_{};
Http::RequestHeaderMap* request_headers_{};
bool do_rewrite_{};

// True if the response map is disabled on this iteration of the filter chain.
bool disabled_{};

// True if the response map matched the response and so we should rewrite
// the response. False otherwise.
bool do_rewrite_{};

// The response_map to use. May be the global response_map or a per-route map.
const ResponseMap::ResponseMapPtr* response_map_{};
};

} // namespace ResponseMapFilter
Expand Down