src/api_proxy/path_matcher/path_matcher.h (142 lines of code) (raw):
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <cstddef>
#include <memory>
#include <set>
#include <sstream>
#include <string>
#include <unordered_map>
#include "src/api_proxy/path_matcher/http_template.h"
#include "src/api_proxy/path_matcher/path_matcher_node.h"
namespace espv2 {
namespace api_proxy {
namespace path_matcher {
// VariableBinding specifies a value for a single field in the request message.
// When transcoding HTTP/REST/JSON to gRPC/proto the request message is
// constructed using the HTTP body and the variable bindings (specified through
// request url).
struct VariableBinding {
// The location of the field in the protobuf message, where the value
// needs to be inserted, e.g. "shelf.theme" would mean the "theme" field
// of the nested "shelf" message of the request protobuf message.
std::vector<std::string> field_path;
// The value to be inserted.
std::string value;
};
inline bool operator==(const VariableBinding& b1, const VariableBinding& b2) {
return b1.field_path == b2.field_path && b1.value == b2.value;
}
template <class Method>
class PathMatcherBuilder; // required for PathMatcher constructor
// The immutable, thread safe PathMatcher stores a mapping from a combination of
// a service (host) name and a HTTP path to your method (MethodInfo*). It is
// constructed with a PathMatcherBuilder and supports one operation: Lookup.
// Clients may use this method to locate your method (MethodInfo*) for a
// combination of service name and HTTP URL path.
//
// Usage example:
// 1) building the PathMatcher:
// PathMatcherBuilder builder(false);
// for each (service_name, http_method, url_path, associated method)
// builder.register(service_name, http_method, url_path, data);
// PathMater matcher = builder.Build();
// 2) lookup:
// MethodInfo * method = matcher.Lookup(service_name, http_method,
// url_path);
// if (method == nullptr) failed to find it.
//
template <class Method>
class PathMatcher {
public:
~PathMatcher(){};
Method Lookup(const std::string& http_method, const std::string& path,
std::vector<VariableBinding>* variable_bindings) const;
Method Lookup(const std::string& http_method, const std::string& path) const;
private:
// Creates a Path Matcher with a Builder by moving the builder's root node.
explicit PathMatcher(PathMatcherBuilder<Method>&& builder);
// A root node shared by all services, i.e. paths of all services will be
// registered to this node.
std::unique_ptr<PathMatcherNode> root_ptr_;
// Holds the set of custom verbs found in configured templates.
std::set<std::string> custom_verbs_;
// Data we store per each registered method
struct MethodData {
Method method;
std::vector<HttpTemplate::Variable> variables;
std::string body_field_path;
};
// The info associated with each method. The path matcher nodes
// will hold pointers to MethodData objects in this vector.
std::vector<std::unique_ptr<MethodData>> methods_;
private:
friend class PathMatcherBuilder<Method>;
};
template <class Method>
using PathMatcherPtr = std::unique_ptr<PathMatcher<Method>>;
// This PathMatcherBuilder is used to register path-WrapperGraph pairs and
// instantiate an immutable, thread safe PathMatcher.
//
// The PathMatcherBuilder itself is NOT THREAD SAFE.
template <class Method>
class PathMatcherBuilder {
public:
PathMatcherBuilder();
~PathMatcherBuilder() {}
// Registers a method.
//
// Registrations are one-to-one. If this function is called more than once, it
// replaces the existing method. Only the last registered method is stored.
// Return false if path is an invalid http template.
bool Register(std::string http_method, std::string path,
std::string body_field_path, Method method);
// Returns a unique_ptr to a thread safe PathMatcher that contains all
// registered path-WrapperGraph pairs. Note the PathMatchBuilder instance
// will be moved so cannot use after invoking Build().
PathMatcherPtr<Method> Build();
private:
// A root node shared by all services, i.e. paths of all services will be
// registered to this node.
std::unique_ptr<PathMatcherNode> root_ptr_;
// The set of custom verbs configured.
// TODO: Perhaps this should not be at this level because there will
// be multiple templates in different services on a server. Consider moving
// this to PathMatcherNode.
std::set<std::string> custom_verbs_;
using MethodData = typename PathMatcher<Method>::MethodData;
std::vector<std::unique_ptr<MethodData>> methods_;
friend class PathMatcher<Method>;
};
void ExtractBindingsFromPath(const std::vector<HttpTemplate::Variable>& vars,
const std::vector<std::string>& parts,
std::vector<VariableBinding>* bindings);
// Converts a request path into a format that can be used to perform a request
// lookup in the PathMatcher trie. This utility method sanitizes the request
// path and then splits the path into slash separated parts. Returns an empty
// vector if the sanitized path is "/".
//
// custom_verbs is a set of configured custom verbs that are used to match
// against any custom verbs in request path. If the request_path contains a
// custom verb not found in custom_verbs, it is treated as a part of the path.
//
// - Strips off query string: "/a?foo=bar" --> "/a"
// - Collapses extra slashes: "///" --> "/"
std::vector<std::string> ExtractRequestParts(
std::string path, const std::set<std::string>& custom_verbs);
// Looks up on a PathMatcherNode.
PathMatcherLookupResult LookupInPathMatcherNode(
const PathMatcherNode& root, const std::vector<std::string>& parts,
const HttpMethod& http_method);
PathMatcherNode::PathInfo TransformHttpTemplate(const HttpTemplate& ht);
template <class Method>
PathMatcher<Method>::PathMatcher(PathMatcherBuilder<Method>&& builder)
: root_ptr_(std::move(builder.root_ptr_)),
custom_verbs_(std::move(builder.custom_verbs_)),
methods_(std::move(builder.methods_)) {}
template <class Method>
Method PathMatcher<Method>::Lookup(
const std::string& http_method, const std::string& path,
std::vector<VariableBinding>* variable_bindings) const {
const std::vector<std::string> parts =
ExtractRequestParts(path, custom_verbs_);
// If service_name has not been registered to ESPv2 and
// strict_service_matching_ is set to false, tries to lookup the method in all
// registered services.
if (root_ptr_ == nullptr) {
return nullptr;
}
PathMatcherLookupResult lookup_result =
LookupInPathMatcherNode(*root_ptr_, parts, http_method);
// Return nullptr if nothing is found.
// Not need to check duplication. Only first item is stored for duplicated
if (lookup_result.data == nullptr) {
return nullptr;
}
MethodData* method_data = reinterpret_cast<MethodData*>(lookup_result.data);
if (variable_bindings != nullptr) {
variable_bindings->clear();
ExtractBindingsFromPath(method_data->variables, parts, variable_bindings);
}
return method_data->method;
}
// TODO: refactor common code with method above
template <class Method>
Method PathMatcher<Method>::Lookup(const std::string& http_method,
const std::string& path) const {
const std::vector<std::string> parts =
ExtractRequestParts(path, custom_verbs_);
// If service_name has not been registered to ESP and strict_service_matching_
// is set to false, tries to lookup the method in all registered services.
if (root_ptr_ == nullptr) {
return nullptr;
}
PathMatcherLookupResult lookup_result =
LookupInPathMatcherNode(*root_ptr_, parts, http_method);
// Return nullptr if nothing is found.
// Not need to check duplication. Only first item is stored for duplicated
if (lookup_result.data == nullptr) {
return nullptr;
}
MethodData* method_data = reinterpret_cast<MethodData*>(lookup_result.data);
return method_data->method;
}
// Initializes the builder with a root Path Segment
template <class Method>
PathMatcherBuilder<Method>::PathMatcherBuilder()
: root_ptr_(new PathMatcherNode()) {}
template <class Method>
PathMatcherPtr<Method> PathMatcherBuilder<Method>::Build() {
return PathMatcherPtr<Method>(new PathMatcher<Method>(std::move(*this)));
}
// This wrapper converts the |http_rule| into a HttpTemplate. Then, inserts the
// template into the trie.
template <class Method>
bool PathMatcherBuilder<Method>::Register(std::string http_method,
std::string http_template,
std::string body_field_path,
Method method) {
std::unique_ptr<HttpTemplate> ht(HttpTemplate::Parse(http_template));
if (nullptr == ht) {
return false;
}
PathMatcherNode::PathInfo path_info = TransformHttpTemplate(*ht);
// Create & initialize a MethodData struct. Then insert its pointer
// into the path matcher trie.
auto method_data = std::unique_ptr<MethodData>(new MethodData());
method_data->method = method;
method_data->variables = std::move(ht->Variables());
method_data->body_field_path = std::move(body_field_path);
if (!root_ptr_->InsertPath(path_info, http_method, method_data.get(), true)) {
return false;
}
// Add the method_data to the methods_ vector for cleanup
methods_.emplace_back(std::move(method_data));
if (!ht->verb().empty()) {
custom_verbs_.insert(ht->verb());
}
return true;
}
} // namespace path_matcher
} // namespace api_proxy
} // namespace espv2