/proc/self/cwd/source/extensions/filters/network/dubbo_proxy/router/route_matcher.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "source/extensions/filters/network/dubbo_proxy/router/route_matcher.h" |
2 | | |
3 | | #include "envoy/config/route/v3/route_components.pb.h" |
4 | | #include "envoy/extensions/filters/network/dubbo_proxy/v3/route.pb.h" |
5 | | |
6 | | #include "source/common/protobuf/utility.h" |
7 | | |
8 | | namespace Envoy { |
9 | | namespace Extensions { |
10 | | namespace NetworkFilters { |
11 | | namespace DubboProxy { |
12 | | namespace Router { |
13 | | |
14 | | RouteEntryImplBase::RouteEntryImplBase( |
15 | | const envoy::extensions::filters::network::dubbo_proxy::v3::Route& route) |
16 | | : cluster_name_(route.route().cluster()), |
17 | 0 | config_headers_(Http::HeaderUtility::buildHeaderDataVector(route.match().headers())) { |
18 | 0 | if (route.route().has_metadata_match()) { |
19 | 0 | const auto filter_it = route.route().metadata_match().filter_metadata().find( |
20 | 0 | Envoy::Config::MetadataFilters::get().ENVOY_LB); |
21 | 0 | if (filter_it != route.route().metadata_match().filter_metadata().end()) { |
22 | 0 | metadata_match_criteria_ = |
23 | 0 | std::make_unique<Envoy::Router::MetadataMatchCriteriaImpl>(filter_it->second); |
24 | 0 | } |
25 | 0 | } |
26 | 0 | if (route.route().cluster_specifier_case() == |
27 | 0 | envoy::extensions::filters::network::dubbo_proxy::v3::RouteAction::ClusterSpecifierCase:: |
28 | 0 | kWeightedClusters) { |
29 | 0 | total_cluster_weight_ = 0UL; |
30 | 0 | for (const auto& cluster : route.route().weighted_clusters().clusters()) { |
31 | 0 | weighted_clusters_.emplace_back(std::make_shared<WeightedClusterEntry>(*this, cluster)); |
32 | 0 | total_cluster_weight_ += weighted_clusters_.back()->clusterWeight(); |
33 | 0 | } |
34 | 0 | ENVOY_LOG(debug, "dubbo route matcher: weighted_clusters_size {}", weighted_clusters_.size()); |
35 | 0 | } |
36 | 0 | } |
37 | | |
38 | 0 | const std::string& RouteEntryImplBase::clusterName() const { return cluster_name_; } |
39 | | |
40 | 0 | const RouteEntry* RouteEntryImplBase::routeEntry() const { return this; } |
41 | | |
42 | 0 | RouteConstSharedPtr RouteEntryImplBase::clusterEntry(uint64_t random_value) const { |
43 | 0 | if (weighted_clusters_.empty()) { |
44 | 0 | ENVOY_LOG(debug, "dubbo route matcher: weighted_clusters_size {}", weighted_clusters_.size()); |
45 | 0 | return shared_from_this(); |
46 | 0 | } |
47 | | |
48 | 0 | return WeightedClusterUtil::pickCluster(weighted_clusters_, total_cluster_weight_, random_value, |
49 | 0 | false); |
50 | 0 | } |
51 | | |
52 | 0 | bool RouteEntryImplBase::headersMatch(const RpcInvocationImpl& invocation) const { |
53 | 0 | if (config_headers_.empty()) { |
54 | 0 | ENVOY_LOG(debug, "dubbo route matcher: no headers match"); |
55 | 0 | return true; |
56 | 0 | } |
57 | | |
58 | 0 | const auto& headers = invocation.attachment().headers(); |
59 | 0 | ENVOY_LOG(debug, "dubbo route matcher: headers size {}, metadata headers size {}", |
60 | 0 | config_headers_.size(), headers.size()); |
61 | 0 | return Http::HeaderUtility::matchHeaders(headers, config_headers_); |
62 | 0 | } |
63 | | |
64 | | RouteEntryImplBase::WeightedClusterEntry::WeightedClusterEntry(const RouteEntryImplBase& parent, |
65 | | const WeightedCluster& cluster) |
66 | | : parent_(parent), cluster_name_(cluster.name()), |
67 | 0 | cluster_weight_(PROTOBUF_GET_WRAPPED_REQUIRED(cluster, weight)) { |
68 | 0 | if (cluster.has_metadata_match()) { |
69 | 0 | const auto filter_it = cluster.metadata_match().filter_metadata().find( |
70 | 0 | Envoy::Config::MetadataFilters::get().ENVOY_LB); |
71 | 0 | if (filter_it != cluster.metadata_match().filter_metadata().end()) { |
72 | |
|
73 | 0 | if (parent.metadata_match_criteria_) { |
74 | 0 | metadata_match_criteria_ = |
75 | 0 | parent.metadata_match_criteria_->mergeMatchCriteria(filter_it->second); |
76 | 0 | } else { |
77 | 0 | metadata_match_criteria_ = |
78 | 0 | std::make_unique<Envoy::Router::MetadataMatchCriteriaImpl>(filter_it->second); |
79 | 0 | } |
80 | 0 | } |
81 | 0 | } |
82 | 0 | } |
83 | | |
84 | | ParameterRouteEntryImpl::ParameterRouteEntryImpl( |
85 | | const envoy::extensions::filters::network::dubbo_proxy::v3::Route& route) |
86 | 0 | : RouteEntryImplBase(route) { |
87 | 0 | for (auto& config : route.match().method().params_match()) { |
88 | 0 | parameter_data_list_.emplace_back(config.first, config.second); |
89 | 0 | } |
90 | 0 | } |
91 | | |
92 | 0 | ParameterRouteEntryImpl::~ParameterRouteEntryImpl() = default; |
93 | | |
94 | | bool ParameterRouteEntryImpl::matchParameter(absl::string_view request_data, |
95 | 0 | const ParameterData& config_data) const { |
96 | 0 | switch (config_data.match_type_) { |
97 | 0 | case Http::HeaderUtility::HeaderMatchType::Value: |
98 | 0 | return config_data.value_.empty() || request_data == config_data.value_; |
99 | 0 | case Http::HeaderUtility::HeaderMatchType::Range: { |
100 | 0 | int64_t value = 0; |
101 | 0 | return absl::SimpleAtoi(request_data, &value) && value >= config_data.range_.start() && |
102 | 0 | value < config_data.range_.end(); |
103 | 0 | } |
104 | 0 | default: |
105 | 0 | PANIC("not handled"); |
106 | 0 | } |
107 | 0 | } |
108 | | |
109 | | RouteConstSharedPtr ParameterRouteEntryImpl::matches(const MessageMetadata& metadata, |
110 | 0 | uint64_t random_value) const { |
111 | 0 | ASSERT(metadata.hasInvocationInfo()); |
112 | 0 | const auto invocation = dynamic_cast<const RpcInvocationImpl*>(&metadata.invocationInfo()); |
113 | 0 | ASSERT(invocation); |
114 | | |
115 | 0 | const auto& parameters = invocation->parameters(); |
116 | 0 | if (parameters.empty()) { |
117 | 0 | return nullptr; |
118 | 0 | } |
119 | | |
120 | 0 | ENVOY_LOG(debug, "dubbo route matcher: parameter name match"); |
121 | 0 | for (auto& config_data : parameter_data_list_) { |
122 | 0 | if (config_data.index_ >= parameters.size()) { |
123 | 0 | ENVOY_LOG(debug, |
124 | 0 | "dubbo route matcher: parameter matching failed, there is no parameter in the " |
125 | 0 | "user request, index '{}'", |
126 | 0 | config_data.index_); |
127 | 0 | return nullptr; |
128 | 0 | } |
129 | | |
130 | 0 | const auto data = parameters.at(config_data.index_)->toString(); |
131 | 0 | if (!data.has_value()) { |
132 | 0 | ENVOY_LOG(debug, |
133 | 0 | "dubbo route matcher: parameter matching failed, the parameter cannot be converted " |
134 | 0 | "to string, index '{}'", |
135 | 0 | config_data.index_); |
136 | 0 | return nullptr; |
137 | 0 | } |
138 | | |
139 | 0 | if (!matchParameter(absl::string_view(data.value().get()), config_data)) { |
140 | 0 | ENVOY_LOG(debug, "dubbo route matcher: parameter matching failed, index '{}', value '{}'", |
141 | 0 | config_data.index_, data.value().get()); |
142 | 0 | return nullptr; |
143 | 0 | } |
144 | 0 | } |
145 | | |
146 | 0 | return clusterEntry(random_value); |
147 | 0 | } |
148 | | |
149 | | ParameterRouteEntryImpl::ParameterData::ParameterData(uint32_t index, |
150 | 0 | const ParameterMatchSpecifier& config) { |
151 | 0 | index_ = index; |
152 | 0 | switch (config.parameter_match_specifier_case()) { |
153 | 0 | case ParameterMatchSpecifier::kExactMatch: |
154 | 0 | match_type_ = Http::HeaderUtility::HeaderMatchType::Value; |
155 | 0 | value_ = config.exact_match(); |
156 | 0 | break; |
157 | 0 | case ParameterMatchSpecifier::kRangeMatch: |
158 | 0 | match_type_ = Http::HeaderUtility::HeaderMatchType::Range; |
159 | 0 | range_.set_start(config.range_match().start()); |
160 | 0 | range_.set_end(config.range_match().end()); |
161 | 0 | break; |
162 | 0 | default: |
163 | 0 | match_type_ = Http::HeaderUtility::HeaderMatchType::Value; |
164 | 0 | break; |
165 | 0 | } |
166 | 0 | } |
167 | | |
168 | | MethodRouteEntryImpl::MethodRouteEntryImpl( |
169 | | const envoy::extensions::filters::network::dubbo_proxy::v3::Route& route) |
170 | 0 | : RouteEntryImplBase(route), method_name_(route.match().method().name()) { |
171 | 0 | if (route.match().method().params_match_size() != 0) { |
172 | 0 | parameter_route_ = std::make_shared<ParameterRouteEntryImpl>(route); |
173 | 0 | } |
174 | 0 | } |
175 | | |
176 | 0 | MethodRouteEntryImpl::~MethodRouteEntryImpl() = default; |
177 | | |
178 | | RouteConstSharedPtr MethodRouteEntryImpl::matches(const MessageMetadata& metadata, |
179 | 0 | uint64_t random_value) const { |
180 | 0 | ASSERT(metadata.hasInvocationInfo()); |
181 | 0 | const auto invocation = dynamic_cast<const RpcInvocationImpl*>(&metadata.invocationInfo()); |
182 | 0 | ASSERT(invocation); |
183 | | |
184 | 0 | if (!RouteEntryImplBase::headersMatch(*invocation)) { |
185 | 0 | ENVOY_LOG(error, "dubbo route matcher: headers not match"); |
186 | 0 | return nullptr; |
187 | 0 | } |
188 | | |
189 | 0 | if (invocation->methodName().empty()) { |
190 | 0 | ENVOY_LOG(error, "dubbo route matcher: there is no method name in the metadata"); |
191 | 0 | return nullptr; |
192 | 0 | } |
193 | | |
194 | 0 | if (!method_name_.match(invocation->methodName())) { |
195 | 0 | ENVOY_LOG(debug, "dubbo route matcher: method matching failed, input method '{}'", |
196 | 0 | invocation->methodName()); |
197 | 0 | return nullptr; |
198 | 0 | } |
199 | | |
200 | 0 | if (parameter_route_) { |
201 | 0 | ENVOY_LOG(debug, "dubbo route matcher: parameter matching is required"); |
202 | 0 | return parameter_route_->matches(metadata, random_value); |
203 | 0 | } |
204 | | |
205 | 0 | return clusterEntry(random_value); |
206 | 0 | } |
207 | | |
208 | | SingleRouteMatcherImpl::SingleRouteMatcherImpl(const RouteConfig& config, |
209 | | Server::Configuration::ServerFactoryContext&) |
210 | 0 | : interface_matcher_(config.interface()), group_(config.group()), version_(config.version()) { |
211 | 0 | using envoy::extensions::filters::network::dubbo_proxy::v3::RouteMatch; |
212 | |
|
213 | 0 | for (const auto& route : config.routes()) { |
214 | 0 | routes_.emplace_back(std::make_shared<MethodRouteEntryImpl>(route)); |
215 | 0 | } |
216 | 0 | ENVOY_LOG(debug, "dubbo route matcher: routes list size {}", routes_.size()); |
217 | 0 | } |
218 | | |
219 | 0 | bool SingleRouteMatcherImpl::matchServiceGroup(const RpcInvocationImpl& invocation) const { |
220 | 0 | if (!group_.has_value() || group_.value().empty()) { |
221 | 0 | return true; |
222 | 0 | } |
223 | | |
224 | 0 | return invocation.serviceGroup().has_value() && invocation.serviceGroup().value() == group_; |
225 | 0 | } |
226 | | |
227 | 0 | bool SingleRouteMatcherImpl::matchServiceVersion(const RpcInvocationImpl& invocation) const { |
228 | 0 | if (!version_.has_value() || version_.value().empty()) { |
229 | 0 | return true; |
230 | 0 | } |
231 | 0 | return invocation.serviceVersion().has_value() && invocation.serviceVersion().value() == version_; |
232 | 0 | } |
233 | | |
234 | 0 | bool SingleRouteMatcherImpl::matchServiceName(const RpcInvocationImpl& invocation) const { |
235 | 0 | return interface_matcher_.match(invocation.serviceName()); |
236 | 0 | } |
237 | | |
238 | 0 | SingleRouteMatcherImpl::InterfaceMatcher::InterfaceMatcher(const std::string& interface_name) { |
239 | 0 | if (interface_name == "*") { |
240 | 0 | impl_ = [](const absl::string_view interface) { return !interface.empty(); }; |
241 | 0 | return; |
242 | 0 | } |
243 | 0 | if (absl::StartsWith(interface_name, "*")) { |
244 | 0 | const std::string suffix = interface_name.substr(1); |
245 | 0 | impl_ = [suffix](const absl::string_view interface) { |
246 | 0 | return interface.size() > suffix.size() && absl::EndsWith(interface, suffix); |
247 | 0 | }; |
248 | 0 | return; |
249 | 0 | } |
250 | 0 | if (absl::EndsWith(interface_name, "*")) { |
251 | 0 | const std::string prefix = interface_name.substr(0, interface_name.size() - 1); |
252 | 0 | impl_ = [prefix](const absl::string_view interface) { |
253 | 0 | return interface.size() > prefix.size() && absl::StartsWith(interface, prefix); |
254 | 0 | }; |
255 | 0 | return; |
256 | 0 | } |
257 | 0 | impl_ = [interface_name](const absl::string_view interface) { |
258 | 0 | return interface == interface_name; |
259 | 0 | }; |
260 | 0 | } |
261 | | |
262 | | RouteConstSharedPtr SingleRouteMatcherImpl::route(const MessageMetadata& metadata, |
263 | 0 | uint64_t random_value) const { |
264 | 0 | ASSERT(metadata.hasInvocationInfo()); |
265 | 0 | const auto invocation = dynamic_cast<const RpcInvocationImpl*>(&metadata.invocationInfo()); |
266 | 0 | ASSERT(invocation); |
267 | | |
268 | 0 | if (matchServiceName(*invocation) && matchServiceVersion(*invocation) && |
269 | 0 | matchServiceGroup(*invocation)) { |
270 | 0 | for (const auto& route : routes_) { |
271 | 0 | RouteConstSharedPtr route_entry = route->matches(metadata, random_value); |
272 | 0 | if (nullptr != route_entry) { |
273 | 0 | return route_entry; |
274 | 0 | } |
275 | 0 | } |
276 | 0 | } else { |
277 | 0 | ENVOY_LOG(debug, "dubbo route matcher: interface matching failed"); |
278 | 0 | } |
279 | | |
280 | 0 | return nullptr; |
281 | 0 | } |
282 | | |
283 | | RouteConfigImpl::RouteConfigImpl(const RouteConfigList& route_config_list, |
284 | 0 | Server::Configuration::ServerFactoryContext& context, bool) { |
285 | 0 | for (const auto& route_config : route_config_list) { |
286 | 0 | route_matcher_list_.emplace_back( |
287 | 0 | std::make_unique<SingleRouteMatcherImpl>(route_config, context)); |
288 | 0 | } |
289 | 0 | ENVOY_LOG(debug, "route matcher list size {}", route_matcher_list_.size()); |
290 | 0 | } |
291 | | |
292 | | RouteConstSharedPtr RouteConfigImpl::route(const MessageMetadata& metadata, |
293 | 0 | uint64_t random_value) const { |
294 | 0 | for (const auto& route_matcher : route_matcher_list_) { |
295 | 0 | auto route = route_matcher->route(metadata, random_value); |
296 | 0 | if (nullptr != route) { |
297 | 0 | return route; |
298 | 0 | } |
299 | 0 | } |
300 | | |
301 | 0 | return nullptr; |
302 | 0 | } |
303 | | |
304 | | } // namespace Router |
305 | | } // namespace DubboProxy |
306 | | } // namespace NetworkFilters |
307 | | } // namespace Extensions |
308 | | } // namespace Envoy |