Skip to content

Commit

Permalink
responsemap rewrite
Browse files Browse the repository at this point in the history
Signed-off-by: Alice Wasko <[email protected]>
  • Loading branch information
Alice Wasko committed May 13, 2024
1 parent ba54725 commit 4c1e336
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 69 deletions.
104 changes: 40 additions & 64 deletions source/common/response_map/response_map.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ namespace ResponseMap {
class BodyFormatter {
public:
BodyFormatter()
: formatter_(std::make_unique<Envoy::Formatter::FormatterImpl>("%LOCAL_REPLY_BODY%")),
: formatter_(std::make_unique<Envoy::Formatter::FormatterImpl>("%LOCAL_REPLY_BODY%", false)),
content_type_(Http::Headers::get().ContentTypeValues.Text) {}

BodyFormatter(const envoy::config::core::v3::SubstitutionFormatString& config,
Server::Configuration::CommonFactoryContext& context)
Server::Configuration::GenericFactoryContext& context)
: formatter_(Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config, context)),
content_type_(
!config.content_type().empty() ? config.content_type()
Expand All @@ -38,8 +38,8 @@ class BodyFormatter {
const Http::ResponseTrailerMap& response_trailers,
const StreamInfo::StreamInfo& stream_info, std::string& body,
absl::string_view& content_type) const {
body =
formatter_->format(request_headers, response_headers, response_trailers, stream_info, body);
body = formatter_->formatWithContext(
{&request_headers, &response_headers, &response_trailers, body}, stream_info);
content_type = content_type_;
}

Expand All @@ -53,61 +53,45 @@ using BodyFormatterPtr = std::unique_ptr<BodyFormatter>;
class ResponseMapper {
public:
ResponseMapper(const envoy::extensions::filters::http::response_map::v3::ResponseMapper& config,
Server::Configuration::CommonFactoryContext& context,
Server::Configuration::FactoryContext& context,
ProtobufMessage::ValidationVisitor& validationVisitor)
: filter_(AccessLog::FilterFactory::fromProto(config.filter(), context.runtime(),
context.api().randomGenerator(),
validationVisitor)) {
: filter_(AccessLog::FilterFactory::fromProto(config.filter(), context)) {
if (config.has_status_code()) {
status_code_ = static_cast<Http::Code>(config.status_code().value());
}
if (config.has_body()) {
body_ = Config::DataSource::read(config.body(), true, context.api());
body_ = THROW_OR_RETURN_VALUE(
Config::DataSource::read(config.body(), true, context.serverFactoryContext().api()),
std::string);
}

if (config.has_body_format_override()) {
body_formatter_ =
std::make_unique<BodyFormatter>(config.body_format_override(), context);
body_formatter_ = std::make_unique<BodyFormatter>(config.body_format_override(), context);
}
}

// Decide if a request/response pair matches this mapper.
bool match(const Http::RequestHeaderMap* request_headers,
const Http::ResponseHeaderMap& response_headers,
StreamInfo::StreamInfo& stream_info) const {
// Set response code on the stream_info because it's used by the StatusCode filter.
// Further, we know that the status header present on the upstream response headers
// is the status we want to match on. It may not be the status we send downstream
// to the client, though, because of rewrites below.
//
// Under normal circumstances we should have a response status by this point, because
// either the upstream set it or the router filter set it. If for whatever reason we
// don't, skip setting the stream info's response code and just let our evaluation
// logic do without it. We can't do much better, and we certaily don't want to throw
// an exception and crash here.
if (response_headers.Status() != nullptr) {
stream_info.setResponseCode(
static_cast<uint32_t>(Http::Utility::getResponseStatus(response_headers)));
}

if (request_headers == nullptr) {
request_headers = Http::StaticEmptyHeaders::get().request_headers.get();
bool matchAndRewrite(const Http::RequestHeaderMap& request_headers,
Http::ResponseHeaderMap& response_headers,
const Http::ResponseTrailerMap& response_trailers,
StreamInfo::StreamInfo& stream_info, Http::Code& code, std::string& body,
BodyFormatter*& final_formatter) const {
// If not matched, just bail out.
if (filter_ == nullptr ||
!filter_->evaluate({&request_headers, &response_headers, &response_trailers},
stream_info)) {
return false;
}

return filter_->evaluate(stream_info, *request_headers, response_headers,
*Http::StaticEmptyHeaders::get().response_trailers);
}

bool rewrite(const Http::RequestHeaderMap&, Http::ResponseHeaderMap& response_headers,
const Http::ResponseTrailerMap&, StreamInfo::StreamInfo&, std::string& body,
BodyFormatter*& final_formatter) const {
if (body_.has_value()) {
body = body_.value();
}

if (status_code_.has_value() &&
Http::Utility::getResponseStatus(response_headers) != enumToInt(status_code_.value())) {
response_headers.setStatus(std::to_string(enumToInt(status_code_.value())));
header_parser_->evaluateHeaders(response_headers, {&request_headers, &response_headers},
stream_info);

if (status_code_.has_value() && code != status_code_.value()) {
code = status_code_.value();
response_headers.setStatus(std::to_string(enumToInt(code)));
stream_info.setResponseCode(static_cast<uint32_t>(code));
}

if (body_formatter_) {
Expand All @@ -130,7 +114,7 @@ class ResponseMapImpl : public ResponseMap {
ResponseMapImpl() : body_formatter_(std::make_unique<BodyFormatter>()) {}

ResponseMapImpl(const envoy::extensions::filters::http::response_map::v3::ResponseMap& config,
Server::Configuration::CommonFactoryContext& context,
Server::Configuration::FactoryContext& context,
ProtobufMessage::ValidationVisitor& validationVisitor)
: body_formatter_(config.has_body_format()
? std::make_unique<BodyFormatter>(config.body_format(), context)
Expand All @@ -140,33 +124,25 @@ class ResponseMapImpl : public ResponseMap {
}
}

bool match(const Http::RequestHeaderMap* request_headers,
const Http::ResponseHeaderMap& response_headers,
StreamInfo::StreamInfo& stream_info) const override {
for (const auto& mapper : mappers_) {
if (mapper->match(request_headers, response_headers, stream_info)) {
return true;
}
}
return false;
}

void rewrite(const Http::RequestHeaderMap* request_headers,
Http::ResponseHeaderMap& response_headers, StreamInfo::StreamInfo& stream_info,
std::string& body, absl::string_view& content_type) const override {
Http::Code& code, std::string& body,
absl::string_view& content_type) const override {
// Set response code to stream_info and response_headers due to:
// 1) StatusCode filter is using response_code from stream_info,
// 2) %RESP(:status)% is from Status() in response_headers.
response_headers.setStatus(std::to_string(enumToInt(code)));
stream_info.setResponseCode(static_cast<uint32_t>(code));

if (request_headers == nullptr) {
request_headers = Http::StaticEmptyHeaders::get().request_headers.get();
}

BodyFormatter* final_formatter{};
for (const auto& mapper : mappers_) {
if (!mapper->match(request_headers, response_headers, stream_info)) {
continue;
}

if (mapper->rewrite(*request_headers, response_headers,
*Http::StaticEmptyHeaders::get().response_trailers, stream_info, body,
final_formatter)) {
if (mapper->matchAndRewrite(*request_headers, response_headers,
*Http::StaticEmptyHeaders::get().response_trailers, stream_info,
code, body, final_formatter)) {
break;
}
}
Expand All @@ -188,7 +164,7 @@ ResponseMapPtr Factory::createDefault() { return std::make_unique<ResponseMapImp

ResponseMapPtr
Factory::create(const envoy::extensions::filters::http::response_map::v3::ResponseMap& config,
Server::Configuration::CommonFactoryContext& context,
Server::Configuration::FactoryContext& context,
ProtobufMessage::ValidationVisitor& validationVisitor) {
return std::make_unique<ResponseMapImpl>(config, context, validationVisitor);
}
Expand Down
7 changes: 4 additions & 3 deletions source/common/response_map/response_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ class ResponseMap {
* @param body response body.
* @param content_type response content_type.
*/

virtual void rewrite(const Http::RequestHeaderMap* request_headers,
Http::ResponseHeaderMap& response_headers,
StreamInfo::StreamInfo& stream_info, std::string& body,
StreamInfo::StreamInfo& stream_info, Http::Code& code, std::string& body,
absl::string_view& content_type) const PURE;

virtual bool match(const Http::RequestHeaderMap* request_headers,
Expand All @@ -44,12 +45,12 @@ class Factory {
*/
static ResponseMapPtr
create(const envoy::extensions::filters::http::response_map::v3::ResponseMap& config,
Server::Configuration::CommonFactoryContext& context,
Server::Configuration::FactoryContext& context,
ProtobufMessage::ValidationVisitor& validationVisitor);

/**
* Create a default ResponseMap object with empty config.
* It is used at places without Server::Configuration::CommonFactoryContext.
* It is used at places without Server::Configuration::FactoryContext.
*/
static ResponseMapPtr createDefault();
};
Expand Down
5 changes: 3 additions & 2 deletions source/extensions/filters/http/response_map/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ namespace ResponseMapFilter {
Http::FilterFactoryCb ResponseMapFilterFactory::createFilterFactoryFromProtoTyped(
const envoy::extensions::filters::http::response_map::v3::ResponseMap& proto_config,
const std::string& stats_prefix, Server::Configuration::FactoryContext& context) {
ResponseMapFilterConfigSharedPtr config =
std::make_shared<ResponseMapFilterConfig>(proto_config, stats_prefix, context);

ResponseMapFilterConfigSharedPtr config = std::make_shared<ResponseMapFilterConfig>(
proto_config, stats_prefix, context); // context.scope()?
return [config](Http::FilterChainFactoryCallbacks& callbacks) -> void {
callbacks.addStreamFilter(std::make_shared<ResponseMapFilter>(config));
};
Expand Down

0 comments on commit 4c1e336

Please sign in to comment.