Skip to content

Commit 2d3466c

Browse files
yuval-ksoloio-bulldozer[bot]
authored andcommitted
Transformation opt (#22)
* do more things as onetime init for more performance * transform cleanup 2 * nicer errors and logs
1 parent 2c99250 commit 2d3466c

File tree

10 files changed

+170
-133
lines changed

10 files changed

+170
-133
lines changed

source/extensions/filters/http/transformation/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ envoy_cc_library(
8282
"@envoy//include/envoy/buffer:buffer_interface",
8383
"@envoy//include/envoy/http:header_map_interface",
8484
"@envoy//source/common/common:macros",
85+
"@envoy//source/common/common:utility_lib",
8586
"@envoy//source/common/protobuf",
8687
"@inja//:inja-lib",
8788
"@json//:json-lib",

source/extensions/filters/http/transformation/body_header_transformer.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace Transformation {
1212
class BodyHeaderTransformer : public Transformer {
1313
public:
1414
void transform(Http::HeaderMap &map, Buffer::Instance &body) const override;
15+
bool passthrough_body() const override { return false; };
1516
};
1617

1718
} // namespace Transformation

source/extensions/filters/http/transformation/inja_transformer.cc

Lines changed: 83 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <iterator>
44

55
#include "common/common/macros.h"
6+
#include "common/common/utility.h"
67

78
// For convenience
89
using namespace inja;
@@ -34,24 +35,23 @@ const Http::HeaderEntry *getHeader(const Http::HeaderMap &header_map,
3435

3536
} // namespace
3637

37-
std::string ExtractorUtil::extract(
38-
const envoy::api::v2::filter::http::Extraction &extractor,
39-
const Http::HeaderMap &header_map) {
38+
Extractor::Extractor(const envoy::api::v2::filter::http::Extraction &extractor)
39+
: headername_(extractor.header()), group_(extractor.subgroup()),
40+
extract_regex_(RegexUtil::parseRegex(extractor.regex())) {}
41+
std::string Extractor::extract(const Http::HeaderMap &header_map) const {
4042
// TODO: should we lowercase them in the config?
41-
const std::string &headername = extractor.header();
42-
const Http::HeaderEntry *header_entry = getHeader(header_map, headername);
43+
const Http::HeaderEntry *header_entry = getHeader(header_map, headername_);
4344
if (!header_entry) {
4445
return "";
4546
}
4647

4748
std::string value(header_entry->value().getStringView());
48-
unsigned int group = extractor.subgroup();
49+
4950
// get and regex
50-
std::regex extract_regex(extractor.regex());
5151
std::smatch regex_result;
52-
if (std::regex_match(value, regex_result, extract_regex)) {
52+
if (std::regex_match(value, regex_result, extract_regex_)) {
5353
std::smatch::iterator submatch_it = regex_result.begin();
54-
for (unsigned i = 0; i < group; i++) {
54+
for (unsigned i = 0; i < group_; i++) {
5555
std::advance(submatch_it, 1);
5656
if (submatch_it == regex_result.end()) {
5757
return "";
@@ -65,7 +65,8 @@ std::string ExtractorUtil::extract(
6565

6666
TransformerInstance::TransformerInstance(
6767
const Http::HeaderMap &header_map,
68-
const std::map<std::string, std::string> &extractions, const json &context)
68+
const std::unordered_map<std::string, std::string> &extractions,
69+
const json &context)
6970
: header_map_(header_map), extractions_(extractions), context_(context) {
7071
env_.add_callback("header", 1,
7172
[this](Arguments args) { return header_callback(args); });
@@ -93,13 +94,59 @@ json TransformerInstance::extracted_callback(Arguments args) {
9394
return "";
9495
}
9596

96-
std::string TransformerInstance::render(const std::string &input) {
97+
std::string TransformerInstance::render(const inja::Template &input) {
9798
return env_.render(input, context_);
9899
}
99100

100101
InjaTransformer::InjaTransformer(
101102
const envoy::api::v2::filter::http::TransformationTemplate &transformation)
102-
: transformation_(transformation) {}
103+
: advanced_templates_(transformation.advanced_templates()),
104+
passthrough_body_(transformation.has_passthrough()) {
105+
inja::ParserConfig parser_config;
106+
inja::LexerConfig lexer_config;
107+
inja::TemplateStorage template_storage;
108+
if (!advanced_templates_) {
109+
parser_config.notation = inja::ElementNotation::Dot;
110+
}
111+
112+
inja::Parser parser(parser_config, lexer_config, template_storage);
113+
114+
const auto &extractors = transformation.extractors();
115+
for (auto it = extractors.begin(); it != extractors.end(); it++) {
116+
extractors_.emplace_back(std::make_pair(it->first, it->second));
117+
}
118+
const auto &headers = transformation.headers();
119+
for (auto it = headers.begin(); it != headers.end(); it++) {
120+
Http::LowerCaseString header_name(it->first);
121+
try {
122+
headers_.emplace_back(std::make_pair(std::move(header_name),
123+
parser.parse(it->second.text())));
124+
} catch (const std::runtime_error &e) {
125+
throw EnvoyException(fmt::format(
126+
"Failed to parse header template '{}': {}", it->first, e.what()));
127+
}
128+
}
129+
130+
switch (transformation.body_transformation_case()) {
131+
case envoy::api::v2::filter::http::TransformationTemplate::kBody: {
132+
try {
133+
body_template_.emplace(parser.parse(transformation.body().text()));
134+
} catch (const std::runtime_error &e) {
135+
throw EnvoyException(
136+
fmt::format("Failed to parse body template {}", e.what()));
137+
}
138+
}
139+
case envoy::api::v2::filter::http::TransformationTemplate::
140+
kMergeExtractorsToBody: {
141+
merged_extractors_to_body_ = true;
142+
}
143+
case envoy::api::v2::filter::http::TransformationTemplate::kPassthrough:
144+
case envoy::api::v2::filter::http::TransformationTemplate::
145+
BODY_TRANSFORMATION_NOT_SET: {
146+
break;
147+
}
148+
}
149+
}
103150

104151
InjaTransformer::~InjaTransformer() {}
105152

@@ -113,15 +160,15 @@ void InjaTransformer::transform(Http::HeaderMap &header_map,
113160
json_body = json::parse(bodystring);
114161
}
115162
// get the extractions
116-
std::map<std::string, std::string> extractions;
117-
118-
const auto &extractors = transformation_.extractors();
163+
std::unordered_map<std::string, std::string> extractions;
164+
if (advanced_templates_) {
165+
extractions.reserve(extractors_.size());
166+
}
119167

120-
for (auto it = extractors.begin(); it != extractors.end(); it++) {
121-
const std::string &name = it->first;
122-
const envoy::api::v2::filter::http::Extraction &extractor = it->second;
123-
if (transformation_.advanced_templates()) {
124-
extractions[name] = ExtractorUtil::extract(extractor, header_map);
168+
for (const auto &named_extractor : extractors_) {
169+
const std::string &name = named_extractor.first;
170+
if (advanced_templates_) {
171+
extractions[name] = named_extractor.second.extract(header_map);
125172
} else {
126173
std::string name_to_split = name;
127174
json *current = &json_body;
@@ -131,62 +178,40 @@ void InjaTransformer::transform(Http::HeaderMap &header_map,
131178
current = &(*current)[field_name];
132179
name_to_split.erase(0, pos + 1);
133180
}
134-
(*current)[name_to_split] = ExtractorUtil::extract(extractor, header_map);
181+
(*current)[name_to_split] = named_extractor.second.extract(header_map);
135182
}
136183
}
137184
// start transforming!
138185
TransformerInstance instance(header_map, extractions, json_body);
139186

140-
if (!transformation_.advanced_templates()) {
141-
instance.useDotNotation();
142-
}
143-
144-
switch (transformation_.body_transformation_case()) {
145-
case envoy::api::v2::filter::http::TransformationTemplate::kBody: {
146-
const std::string &input = transformation_.body().text();
147-
auto output = instance.render(input);
148-
187+
// Body transform:
188+
auto replace_body = [&](std::string &output) {
149189
// remove content length, as we have new body.
150190
header_map.removeContentLength();
151191
// replace body
152192
body.drain(body.length());
153193
body.add(output);
154194
header_map.insertContentLength().value(body.length());
155-
break;
156-
}
195+
};
157196

158-
case envoy::api::v2::filter::http::TransformationTemplate::
159-
kMergeExtractorsToBody: {
197+
if (body_template_.has_value()) {
198+
std::string output = instance.render(body_template_.value());
199+
replace_body(output);
200+
} else if (merged_extractors_to_body_) {
160201
std::string output = json_body.dump();
161-
162-
// remove content length, as we have new body.
163-
header_map.removeContentLength();
164-
// replace body
165-
body.drain(body.length());
166-
body.add(output);
167-
header_map.insertContentLength().value(body.length());
168-
break;
169-
}
170-
case envoy::api::v2::filter::http::TransformationTemplate::kPassthrough:
171-
case envoy::api::v2::filter::http::TransformationTemplate::
172-
BODY_TRANSFORMATION_NOT_SET: {
173-
break;
202+
replace_body(output);
174203
}
175-
}
176-
177-
// add headers
178-
const auto &headers = transformation_.headers();
179204

180-
for (auto it = headers.begin(); it != headers.end(); it++) {
181-
std::string name = it->first;
182-
auto lkname = Http::LowerCaseString(std::move(name));
183-
const envoy::api::v2::filter::http::InjaTemplate &text = it->second;
184-
std::string output = instance.render(text.text());
205+
// Headers transform:
206+
for (const auto &templated_header : headers_) {
207+
std::string output = instance.render(templated_header.second);
185208
// remove existing header
186-
header_map.remove(lkname);
209+
header_map.remove(templated_header.first);
187210
// TODO(yuval-k): Do we need to support intentional empty headers?
188211
if (!output.empty()) {
189-
header_map.addCopy(lkname, output);
212+
// we can add the key as reference as the headers_ lifetime is as the
213+
// route's
214+
header_map.addReferenceKey(templated_header.first, output);
190215
}
191216
}
192217
}

source/extensions/filters/http/transformation/inja_transformer.h

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,33 +21,34 @@ namespace Transformation {
2121

2222
class TransformerInstance {
2323
public:
24-
TransformerInstance(const Http::HeaderMap &header_map,
25-
const std::map<std::string, std::string> &extractions,
26-
const nlohmann::json &context);
24+
TransformerInstance(
25+
const Http::HeaderMap &header_map,
26+
const std::unordered_map<std::string, std::string> &extractions,
27+
const nlohmann::json &context);
2728
// header_value(name)
2829
// extracted_value(name, index)
2930
nlohmann::json header_callback(inja::Arguments args);
3031

3132
nlohmann::json extracted_callback(inja::Arguments args);
3233

33-
std::string render(const std::string &input);
34-
35-
void useDotNotation() {
36-
env_.set_element_notation(inja::ElementNotation::Dot);
37-
}
34+
std::string render(const inja::Template &input);
3835

3936
private:
4037
inja::Environment env_;
4138
const Http::HeaderMap &header_map_;
42-
const std::map<std::string, std::string> &extractions_;
39+
const std::unordered_map<std::string, std::string> &extractions_;
4340
const nlohmann::json &context_;
4441
};
4542

46-
class ExtractorUtil {
43+
class Extractor {
4744
public:
48-
static std::string
49-
extract(const envoy::api::v2::filter::http::Extraction &extractor,
50-
const Http::HeaderMap &header_map);
45+
Extractor(const envoy::api::v2::filter::http::Extraction &extractor);
46+
std::string extract(const Http::HeaderMap &header_map) const;
47+
48+
private:
49+
const Http::LowerCaseString headername_;
50+
const unsigned int group_;
51+
const std::regex extract_regex_;
5152
};
5253

5354
class InjaTransformer : public Transformer {
@@ -57,6 +58,7 @@ class InjaTransformer : public Transformer {
5758
~InjaTransformer();
5859

5960
void transform(Http::HeaderMap &map, Buffer::Instance &body) const override;
61+
bool passthrough_body() const override { return passthrough_body_; };
6062

6163
private:
6264
/*
@@ -69,7 +71,13 @@ class InjaTransformer : public Transformer {
6971
7072
std::aligned_storage<TransformerImplSize, TransformerImplAlign>::type impl_;
7173
*/
72-
const envoy::api::v2::filter::http::TransformationTemplate &transformation_;
74+
bool advanced_templates_{};
75+
bool passthrough_body_{};
76+
std::vector<std::pair<std::string, Extractor>> extractors_;
77+
std::vector<std::pair<Http::LowerCaseString, inja::Template>> headers_;
78+
79+
absl::optional<inja::Template> body_template_;
80+
bool merged_extractors_to_body_{};
7381
};
7482

7583
} // namespace Transformation

0 commit comments

Comments
 (0)