16 #include "google/cloud/status_or.h" 17 #include <google/api/annotations.pb.h> 18 #include <google/protobuf/descriptor.h> 19 #include <grpcpp/channel.h> 20 #include <grpcpp/completion_queue.h> 21 #include <grpcpp/generic/async_generic_service.h> 22 #include <grpcpp/generic/generic_stub.h> 23 #include <grpcpp/server.h> 24 #include <grpcpp/server_builder.h> 25 #include <grpcpp/server_context.h> 30 namespace spanner_testing {
33 #if !defined(__clang__) && \ 34 (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 9)) 53 std::multimap<std::string, std::string> GetMetadata(
54 grpc::ClientContext& context) {
56 grpc::ServerBuilder builder;
57 grpc::AsyncGenericService generic_service;
58 builder.RegisterAsyncGenericService(&generic_service);
59 auto srv_cq = builder.AddCompletionQueue();
60 auto server = builder.BuildAndStart();
63 grpc::GenericStub generic_stub(
64 server->InProcessChannel(grpc::ChannelArguments()));
65 grpc::CompletionQueue cli_cq;
67 generic_stub.PrepareCall(&context,
"made_up_method", &cli_cq);
68 cli_stream->StartCall(
nullptr);
71 cli_cq.Next(&dummy, &ok);
74 grpc::GenericServerContext server_context;
75 grpc::GenericServerAsyncReaderWriter reader_writer(&server_context);
76 generic_service.RequestCall(&server_context, &reader_writer, srv_cq.get(),
77 srv_cq.get(),
nullptr);
78 srv_cq->Next(&dummy, &ok);
81 std::multimap<std::string, std::string> res;
82 auto const& cli_md = server_context.client_metadata();
83 std::transform(cli_md.begin(), cli_md.end(), std::inserter(res, res.begin()),
84 [](std::pair<grpc::string_ref, grpc::string_ref>
const& md) {
85 return std::make_pair(
86 std::string(md.first.data(), md.first.length()),
87 std::string(md.second.data(), md.second.length()));
91 server->Shutdown(std::chrono::system_clock::now());
95 while (srv_cq->Next(&dummy, &ok))
97 while (cli_cq.Next(&dummy, &ok))
107 StatusOr<std::map<std::string, std::string> > ExtractMDFromHeader(
108 std::string header) {
109 std::map<std::string, std::string> res;
110 std::regex pair_re(
"[^&]+");
111 for (std::sregex_iterator i =
112 std::sregex_iterator(header.begin(), header.end(), pair_re);
113 i != std::sregex_iterator(); ++i) {
114 std::regex assign_re(
"([^=]+)=([^=]+)");
115 std::smatch match_res;
116 std::string s = i->str();
117 bool const matched = std::regex_match(s, match_res, assign_re);
120 StatusCode::kInvalidArgument,
121 "Bad header format. The header should be a series of \"a=b\" " 122 "delimited with \"&\", but is \"" +
125 bool const inserted =
126 res.insert(std::make_pair(match_res[1].str(), match_res[2].str()))
130 StatusCode::kInvalidArgument,
131 "Param " + match_res[1].str() +
" is listed more then once");
137 StatusOr<std::map<std::string, std::string> > ExtractMDFromHeaders(
138 std::multimap<std::string, std::string>
const& headers) {
139 auto param_header = headers.equal_range(
"x-goog-request-params");
140 if (param_header.first == param_header.second) {
141 return Status(StatusCode::kInvalidArgument,
"Expected header not found");
143 if (std::distance(param_header.first, param_header.second) > 1U) {
144 return Status(StatusCode::kInvalidArgument,
"Multiple headers found");
146 return ExtractMDFromHeader(param_header.first->second);
150 bool ValueMatchesPattern(std::string val, std::string pattern) {
151 std::string regexified_pattern =
152 regex_replace(pattern, std::regex(
"\\*"), std::string(
"[^/]+"));
153 return std::regex_match(val, std::regex(regexified_pattern));
163 StatusOr<std::map<std::string, std::string> > ExtractParamsFromMethod(
164 std::string
const& method) {
166 google::protobuf::DescriptorPool::generated_pool()->FindMethodByName(
169 if (method_desc ==
nullptr) {
170 return Status(StatusCode::kInvalidArgument,
171 "Method " + method +
" is unknown.");
173 auto options = method_desc->options();
174 if (!options.HasExtension(google::api::http)) {
175 return Status(StatusCode::kInvalidArgument,
176 "Method " + method +
" doesn't have a http option.");
178 auto const& http = options.GetExtension(google::api::http);
180 if (!http.get().empty()) {
181 pattern = http.get();
183 if (!http.put().empty()) {
184 pattern = http.put();
186 if (!http.post().empty()) {
187 pattern = http.post();
189 if (!http.delete_().empty()) {
190 pattern = http.delete_();
192 if (!http.patch().empty()) {
193 pattern = http.patch();
195 if (http.has_custom()) {
196 pattern = http.custom().path();
199 if (pattern.empty()) {
201 StatusCode::kInvalidArgument,
202 "Method " + method +
" has a http option with an empty pattern.");
205 std::regex subst_re(
"\\{([^{}=]+)=([^{}=]+)\\}");
206 std::map<std::string, std::string> res;
207 for (std::sregex_iterator i =
208 std::sregex_iterator(pattern.begin(), pattern.end(), subst_re);
209 i != std::sregex_iterator(); ++i) {
210 std::string
const& param = (*i)[1].str();
211 std::string
const& expected_pattern = (*i)[2].str();
212 res.insert(std::make_pair(param, expected_pattern));
225 Status
IsContextMDValid(grpc::ClientContext& context, std::string
const& method,
226 std::string
const& api_client_header) {
227 auto headers = GetMetadata(context);
230 auto md = ExtractMDFromHeaders(headers);
237 auto params = ExtractParamsFromMethod(method);
239 return params.status();
242 for (
auto const& param_pattern : *params) {
243 auto const& param = param_pattern.first;
244 auto const& expected_pattern = param_pattern.second;
245 auto found_it = md->find(param);
246 if (found_it == md->end()) {
247 return Status(StatusCode::kInvalidArgument,
248 "Expected param \"" + param +
"\" not found in metadata");
250 if (!ValueMatchesPattern(found_it->second, expected_pattern)) {
251 return Status(StatusCode::kInvalidArgument,
252 "Expected param \"" + param +
253 "\" found, but its value (\"" + found_it->second +
254 "\") does not satisfy the pattern (\"" +
255 expected_pattern +
"\").");
259 auto found_api_client_header = headers.find(
"x-goog-api-client");
260 if (found_api_client_header == headers.end()) {
261 return Status(StatusCode::kInvalidArgument,
262 "Expected x-goog-api-client metadata");
264 if (found_api_client_header->second != api_client_header) {
265 return Status(StatusCode::kInvalidArgument,
266 "Expected x-goog-api-client to be " + api_client_header +
267 ", was " + found_api_client_header->second);
Status IsContextMDValid(grpc::ClientContext &, std::string const &, std::string const &)
Verify that the metadata in the context is appropriate for a gRPC method.
#define SPANNER_CLIENT_NS