3
3
#include < iterator>
4
4
5
5
#include " common/common/macros.h"
6
+ #include " common/common/utility.h"
6
7
7
8
// For convenience
8
9
using namespace inja ;
@@ -34,24 +35,23 @@ const Http::HeaderEntry *getHeader(const Http::HeaderMap &header_map,
34
35
35
36
} // namespace
36
37
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 {
40
42
// 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_);
43
44
if (!header_entry) {
44
45
return " " ;
45
46
}
46
47
47
48
std::string value (header_entry->value ().getStringView ());
48
- unsigned int group = extractor. subgroup ();
49
+
49
50
// get and regex
50
- std::regex extract_regex (extractor.regex ());
51
51
std::smatch regex_result;
52
- if (std::regex_match (value, regex_result, extract_regex )) {
52
+ if (std::regex_match (value, regex_result, extract_regex_ )) {
53
53
std::smatch::iterator submatch_it = regex_result.begin ();
54
- for (unsigned i = 0 ; i < group ; i++) {
54
+ for (unsigned i = 0 ; i < group_ ; i++) {
55
55
std::advance (submatch_it, 1 );
56
56
if (submatch_it == regex_result.end ()) {
57
57
return " " ;
@@ -65,7 +65,8 @@ std::string ExtractorUtil::extract(
65
65
66
66
TransformerInstance::TransformerInstance (
67
67
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)
69
70
: header_map_(header_map), extractions_(extractions), context_(context) {
70
71
env_.add_callback (" header" , 1 ,
71
72
[this ](Arguments args) { return header_callback (args); });
@@ -93,13 +94,59 @@ json TransformerInstance::extracted_callback(Arguments args) {
93
94
return " " ;
94
95
}
95
96
96
- std::string TransformerInstance::render (const std::string &input) {
97
+ std::string TransformerInstance::render (const inja::Template &input) {
97
98
return env_.render (input, context_);
98
99
}
99
100
100
101
InjaTransformer::InjaTransformer (
101
102
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
+ }
103
150
104
151
InjaTransformer::~InjaTransformer () {}
105
152
@@ -113,15 +160,15 @@ void InjaTransformer::transform(Http::HeaderMap &header_map,
113
160
json_body = json::parse (bodystring);
114
161
}
115
162
// 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
+ }
119
167
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);
125
172
} else {
126
173
std::string name_to_split = name;
127
174
json *current = &json_body;
@@ -131,62 +178,40 @@ void InjaTransformer::transform(Http::HeaderMap &header_map,
131
178
current = &(*current)[field_name];
132
179
name_to_split.erase (0 , pos + 1 );
133
180
}
134
- (*current)[name_to_split] = ExtractorUtil:: extract (extractor, header_map);
181
+ (*current)[name_to_split] = named_extractor. second . extract (header_map);
135
182
}
136
183
}
137
184
// start transforming!
138
185
TransformerInstance instance (header_map, extractions, json_body);
139
186
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) {
149
189
// remove content length, as we have new body.
150
190
header_map.removeContentLength ();
151
191
// replace body
152
192
body.drain (body.length ());
153
193
body.add (output);
154
194
header_map.insertContentLength ().value (body.length ());
155
- break ;
156
- }
195
+ };
157
196
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_) {
160
201
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);
174
203
}
175
- }
176
-
177
- // add headers
178
- const auto &headers = transformation_.headers ();
179
204
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 );
185
208
// remove existing header
186
- header_map.remove (lkname );
209
+ header_map.remove (templated_header. first );
187
210
// TODO(yuval-k): Do we need to support intentional empty headers?
188
211
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);
190
215
}
191
216
}
192
217
}
0 commit comments