| // |
| // Copyright 2022 gRPC authors. |
| // |
| // 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. |
| // |
| |
| #include <grpc/support/port_platform.h> |
| |
| #include "src/core/lib/service_config/service_config_impl.h" |
| |
| #include <string.h> |
| |
| #include <map> |
| #include <string> |
| #include <utility> |
| |
| #include "absl/memory/memory.h" |
| #include "absl/strings/str_cat.h" |
| |
| #include <grpc/support/log.h> |
| |
| #include "src/core/lib/config/core_configuration.h" |
| #include "src/core/lib/gprpp/memory.h" |
| #include "src/core/lib/json/json.h" |
| #include "src/core/lib/service_config/service_config_parser.h" |
| #include "src/core/lib/slice/slice_internal.h" |
| #include "src/core/lib/slice/slice_refcount.h" |
| |
| namespace grpc_core { |
| |
| RefCountedPtr<ServiceConfig> ServiceConfigImpl::Create( |
| const grpc_channel_args* args, absl::string_view json_string, |
| grpc_error_handle* error) { |
| GPR_DEBUG_ASSERT(error != nullptr); |
| Json json = Json::Parse(json_string, error); |
| if (*error != GRPC_ERROR_NONE) return nullptr; |
| return MakeRefCounted<ServiceConfigImpl>(args, std::string(json_string), |
| std::move(json), error); |
| } |
| |
| ServiceConfigImpl::ServiceConfigImpl(const grpc_channel_args* args, |
| std::string json_string, Json json, |
| grpc_error_handle* error) |
| : json_string_(std::move(json_string)), json_(std::move(json)) { |
| GPR_DEBUG_ASSERT(error != nullptr); |
| if (json_.type() != Json::Type::OBJECT) { |
| *error = |
| GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON value is not an object"); |
| return; |
| } |
| std::vector<grpc_error_handle> error_list; |
| grpc_error_handle global_error = GRPC_ERROR_NONE; |
| parsed_global_configs_ = |
| CoreConfiguration::Get().service_config_parser().ParseGlobalParameters( |
| args, json_, &global_error); |
| if (global_error != GRPC_ERROR_NONE) error_list.push_back(global_error); |
| grpc_error_handle local_error = ParsePerMethodParams(args); |
| if (local_error != GRPC_ERROR_NONE) error_list.push_back(local_error); |
| if (!error_list.empty()) { |
| *error = GRPC_ERROR_CREATE_FROM_VECTOR("Service config parsing error", |
| &error_list); |
| } |
| } |
| |
| ServiceConfigImpl::~ServiceConfigImpl() { |
| for (auto& p : parsed_method_configs_map_) { |
| grpc_slice_unref_internal(p.first); |
| } |
| } |
| |
| grpc_error_handle ServiceConfigImpl::ParseJsonMethodConfig( |
| const grpc_channel_args* args, const Json& json) { |
| std::vector<grpc_error_handle> error_list; |
| // Parse method config with each registered parser. |
| auto parsed_configs = |
| absl::make_unique<ServiceConfigParser::ParsedConfigVector>(); |
| grpc_error_handle parser_error = GRPC_ERROR_NONE; |
| *parsed_configs = |
| CoreConfiguration::Get().service_config_parser().ParsePerMethodParameters( |
| args, json, &parser_error); |
| if (parser_error != GRPC_ERROR_NONE) { |
| error_list.push_back(parser_error); |
| } |
| parsed_method_config_vectors_storage_.push_back(std::move(parsed_configs)); |
| const auto* vector_ptr = parsed_method_config_vectors_storage_.back().get(); |
| // Add an entry for each path. |
| bool found_name = false; |
| auto it = json.object_value().find("name"); |
| if (it != json.object_value().end()) { |
| if (it->second.type() != Json::Type::ARRAY) { |
| error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
| "field:name error:not of type Array")); |
| return GRPC_ERROR_CREATE_FROM_VECTOR("methodConfig", &error_list); |
| } |
| const Json::Array& name_array = it->second.array_value(); |
| for (const Json& name : name_array) { |
| grpc_error_handle parse_error = GRPC_ERROR_NONE; |
| std::string path = ParseJsonMethodName(name, &parse_error); |
| if (parse_error != GRPC_ERROR_NONE) { |
| error_list.push_back(parse_error); |
| } else { |
| found_name = true; |
| if (path.empty()) { |
| if (default_method_config_vector_ != nullptr) { |
| error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
| "field:name error:multiple default method configs")); |
| } |
| default_method_config_vector_ = vector_ptr; |
| } else { |
| grpc_slice key = grpc_slice_from_copied_string(path.c_str()); |
| // If the key is not already present in the map, this will |
| // store a ref to the key in the map. |
| auto& value = parsed_method_configs_map_[key]; |
| if (value != nullptr) { |
| error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
| "field:name error:multiple method configs with same name")); |
| // The map entry already existed, so we need to unref the |
| // key we just created. |
| grpc_slice_unref_internal(key); |
| } else { |
| value = vector_ptr; |
| } |
| } |
| } |
| } |
| } |
| if (!found_name) { |
| parsed_method_config_vectors_storage_.pop_back(); |
| } |
| return GRPC_ERROR_CREATE_FROM_VECTOR("methodConfig", &error_list); |
| } |
| |
| grpc_error_handle ServiceConfigImpl::ParsePerMethodParams( |
| const grpc_channel_args* args) { |
| std::vector<grpc_error_handle> error_list; |
| auto it = json_.object_value().find("methodConfig"); |
| if (it != json_.object_value().end()) { |
| if (it->second.type() != Json::Type::ARRAY) { |
| error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
| "field:methodConfig error:not of type Array")); |
| } |
| for (const Json& method_config : it->second.array_value()) { |
| if (method_config.type() != Json::Type::OBJECT) { |
| error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
| "field:methodConfig error:not of type Object")); |
| continue; |
| } |
| grpc_error_handle error = ParseJsonMethodConfig(args, method_config); |
| if (error != GRPC_ERROR_NONE) { |
| error_list.push_back(error); |
| } |
| } |
| } |
| return GRPC_ERROR_CREATE_FROM_VECTOR("Method Params", &error_list); |
| } |
| |
| std::string ServiceConfigImpl::ParseJsonMethodName(const Json& json, |
| grpc_error_handle* error) { |
| if (json.type() != Json::Type::OBJECT) { |
| *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
| "field:name error:type is not object"); |
| return ""; |
| } |
| // Find service name. |
| const std::string* service_name = nullptr; |
| auto it = json.object_value().find("service"); |
| if (it != json.object_value().end() && |
| it->second.type() != Json::Type::JSON_NULL) { |
| if (it->second.type() != Json::Type::STRING) { |
| *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
| "field:name error: field:service error:not of type string"); |
| return ""; |
| } |
| if (!it->second.string_value().empty()) { |
| service_name = &it->second.string_value(); |
| } |
| } |
| const std::string* method_name = nullptr; |
| // Find method name. |
| it = json.object_value().find("method"); |
| if (it != json.object_value().end() && |
| it->second.type() != Json::Type::JSON_NULL) { |
| if (it->second.type() != Json::Type::STRING) { |
| *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
| "field:name error: field:method error:not of type string"); |
| return ""; |
| } |
| if (!it->second.string_value().empty()) { |
| method_name = &it->second.string_value(); |
| } |
| } |
| // If neither service nor method are specified, it's the default. |
| // Method name may not be specified without service name. |
| if (service_name == nullptr) { |
| if (method_name != nullptr) { |
| *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
| "field:name error:method name populated without service name"); |
| } |
| return ""; |
| } |
| // Construct path. |
| return absl::StrCat("/", *service_name, "/", |
| method_name == nullptr ? "" : *method_name); |
| } |
| |
| const ServiceConfigParser::ParsedConfigVector* |
| ServiceConfigImpl::GetMethodParsedConfigVector(const grpc_slice& path) const { |
| if (parsed_method_configs_map_.empty()) { |
| return default_method_config_vector_; |
| } |
| // Try looking up the full path in the map. |
| auto it = parsed_method_configs_map_.find(path); |
| if (it != parsed_method_configs_map_.end()) return it->second; |
| // If we didn't find a match for the path, try looking for a wildcard |
| // entry (i.e., change "/service/method" to "/service/"). |
| UniquePtr<char> path_str(grpc_slice_to_c_string(path)); |
| char* sep = strrchr(path_str.get(), '/'); |
| if (sep == nullptr) return nullptr; // Shouldn't ever happen. |
| sep[1] = '\0'; |
| grpc_slice wildcard_path = grpc_slice_from_static_string(path_str.get()); |
| it = parsed_method_configs_map_.find(wildcard_path); |
| if (it != parsed_method_configs_map_.end()) return it->second; |
| // Try default method config, if set. |
| return default_method_config_vector_; |
| } |
| |
| } // namespace grpc_core |