1 //
2 // Copyright 2016 gRPC authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16
17 #ifndef GRPC_CORE_LIB_TRANSPORT_SERVICE_CONFIG_H
18 #define GRPC_CORE_LIB_TRANSPORT_SERVICE_CONFIG_H
19
20 #include <grpc/support/port_platform.h>
21
22 #include <grpc/impl/codegen/grpc_types.h>
23 #include <grpc/support/string_util.h>
24
25 #include "src/core/lib/gprpp/inlined_vector.h"
26 #include "src/core/lib/gprpp/ref_counted_ptr.h"
27 #include "src/core/lib/json/json.h"
28 #include "src/core/lib/slice/slice_hash_table.h"
29
30 // The main purpose of the code here is to parse the service config in
31 // JSON form, which will look like this:
32 //
33 // {
34 // "loadBalancingPolicy": "string", // optional
35 // "methodConfig": [ // array of one or more method_config objects
36 // {
37 // "name": [ // array of one or more name objects
38 // {
39 // "service": "string", // required
40 // "method": "string", // optional
41 // }
42 // ],
43 // // remaining fields are optional.
44 // // see
45 // https://developers.google.com/protocol-buffers/docs/proto3#json
46 // // for format details.
47 // "waitForReady": bool,
48 // "timeout": "duration_string",
49 // "maxRequestMessageBytes": "int64_string",
50 // "maxResponseMessageBytes": "int64_string",
51 // }
52 // ]
53 // }
54
55 namespace grpc_core {
56
57 class ServiceConfig {
58 public:
59 /// Creates a new service config from parsing \a json_string.
60 /// Returns null on parse error.
61 static UniquePtr<ServiceConfig> Create(const char* json);
62
63 ~ServiceConfig();
64
65 /// Invokes \a process_json() for each global parameter in the service
66 /// config. \a arg is passed as the second argument to \a process_json().
67 template <typename T>
68 using ProcessJson = void (*)(const grpc_json*, T*);
69 template <typename T>
70 void ParseGlobalParams(ProcessJson<T> process_json, T* arg) const;
71
72 /// Gets the LB policy name from \a service_config.
73 /// Returns NULL if no LB policy name was specified.
74 /// Caller does NOT take ownership.
75 const char* GetLoadBalancingPolicyName() const;
76
77 /// Creates a method config table based on the data in \a json.
78 /// The table's keys are request paths. The table's value type is
79 /// returned by \a create_value(), based on data parsed from the JSON tree.
80 /// Returns null on error.
81 template <typename T>
82 using CreateValue = RefCountedPtr<T> (*)(const grpc_json* method_config_json);
83 template <typename T>
84 RefCountedPtr<SliceHashTable<RefCountedPtr<T>>> CreateMethodConfigTable(
85 CreateValue<T> create_value);
86
87 /// A helper function for looking up values in the table returned by
88 /// \a CreateMethodConfigTable().
89 /// Gets the method config for the specified \a path, which should be of
90 /// the form "/service/method".
91 /// Returns null if the method has no config.
92 /// Caller does NOT own a reference to the result.
93 template <typename T>
94 static RefCountedPtr<T> MethodConfigTableLookup(
95 const SliceHashTable<RefCountedPtr<T>>& table, grpc_slice path);
96
97 private:
98 // So New() can call our private ctor.
99 template <typename T, typename... Args>
100 friend T* New(Args&&... args);
101
102 // Takes ownership of \a json_tree.
103 ServiceConfig(UniquePtr<char> json_string, grpc_json* json_tree);
104
105 // Returns the number of names specified in the method config \a json.
106 static int CountNamesInMethodConfig(grpc_json* json);
107
108 // Returns a path string for the JSON name object specified by \a json.
109 // Returns null on error.
110 static UniquePtr<char> ParseJsonMethodName(grpc_json* json);
111
112 // Parses the method config from \a json. Adds an entry to \a entries for
113 // each name found, incrementing \a idx for each entry added.
114 // Returns false on error.
115 template <typename T>
116 static bool ParseJsonMethodConfig(
117 grpc_json* json, CreateValue<T> create_value,
118 typename SliceHashTable<RefCountedPtr<T>>::Entry* entries, size_t* idx);
119
120 UniquePtr<char> json_string_; // Underlying storage for json_tree.
121 grpc_json* json_tree_;
122 };
123
124 //
125 // implementation -- no user-serviceable parts below
126 //
127
128 template <typename T>
ParseGlobalParams(ProcessJson<T> process_json,T * arg)129 void ServiceConfig::ParseGlobalParams(ProcessJson<T> process_json,
130 T* arg) const {
131 if (json_tree_->type != GRPC_JSON_OBJECT || json_tree_->key != nullptr) {
132 return;
133 }
134 for (grpc_json* field = json_tree_->child; field != nullptr;
135 field = field->next) {
136 if (field->key == nullptr) return;
137 if (strcmp(field->key, "methodConfig") == 0) continue;
138 process_json(field, arg);
139 }
140 }
141
142 template <typename T>
ParseJsonMethodConfig(grpc_json * json,CreateValue<T> create_value,typename SliceHashTable<RefCountedPtr<T>>::Entry * entries,size_t * idx)143 bool ServiceConfig::ParseJsonMethodConfig(
144 grpc_json* json, CreateValue<T> create_value,
145 typename SliceHashTable<RefCountedPtr<T>>::Entry* entries, size_t* idx) {
146 // Construct value.
147 RefCountedPtr<T> method_config = create_value(json);
148 if (method_config == nullptr) return false;
149 // Construct list of paths.
150 InlinedVector<UniquePtr<char>, 10> paths;
151 for (grpc_json* child = json->child; child != nullptr; child = child->next) {
152 if (child->key == nullptr) continue;
153 if (strcmp(child->key, "name") == 0) {
154 if (child->type != GRPC_JSON_ARRAY) return false;
155 for (grpc_json* name = child->child; name != nullptr; name = name->next) {
156 UniquePtr<char> path = ParseJsonMethodName(name);
157 if (path == nullptr) return false;
158 paths.push_back(std::move(path));
159 }
160 }
161 }
162 if (paths.size() == 0) return false; // No names specified.
163 // Add entry for each path.
164 for (size_t i = 0; i < paths.size(); ++i) {
165 entries[*idx].key = grpc_slice_from_copied_string(paths[i].get());
166 entries[*idx].value = method_config; // Takes a new ref.
167 ++*idx;
168 }
169 // Success.
170 return true;
171 }
172
173 template <typename T>
174 RefCountedPtr<SliceHashTable<RefCountedPtr<T>>>
CreateMethodConfigTable(CreateValue<T> create_value)175 ServiceConfig::CreateMethodConfigTable(CreateValue<T> create_value) {
176 // Traverse parsed JSON tree.
177 if (json_tree_->type != GRPC_JSON_OBJECT || json_tree_->key != nullptr) {
178 return nullptr;
179 }
180 size_t num_entries = 0;
181 typename SliceHashTable<RefCountedPtr<T>>::Entry* entries = nullptr;
182 for (grpc_json* field = json_tree_->child; field != nullptr;
183 field = field->next) {
184 if (field->key == nullptr) return nullptr;
185 if (strcmp(field->key, "methodConfig") == 0) {
186 if (entries != nullptr) return nullptr; // Duplicate.
187 if (field->type != GRPC_JSON_ARRAY) return nullptr;
188 // Find number of entries.
189 for (grpc_json* method = field->child; method != nullptr;
190 method = method->next) {
191 int count = CountNamesInMethodConfig(method);
192 if (count <= 0) return nullptr;
193 num_entries += static_cast<size_t>(count);
194 }
195 // Populate method config table entries.
196 entries = static_cast<typename SliceHashTable<RefCountedPtr<T>>::Entry*>(
197 gpr_zalloc(num_entries *
198 sizeof(typename SliceHashTable<RefCountedPtr<T>>::Entry)));
199 size_t idx = 0;
200 for (grpc_json* method = field->child; method != nullptr;
201 method = method->next) {
202 if (!ParseJsonMethodConfig(method, create_value, entries, &idx)) {
203 for (size_t i = 0; i < idx; ++i) {
204 grpc_slice_unref_internal(entries[i].key);
205 entries[i].value.reset();
206 }
207 gpr_free(entries);
208 return nullptr;
209 }
210 }
211 GPR_ASSERT(idx == num_entries);
212 }
213 }
214 // Instantiate method config table.
215 RefCountedPtr<SliceHashTable<RefCountedPtr<T>>> method_config_table;
216 if (entries != nullptr) {
217 method_config_table =
218 SliceHashTable<RefCountedPtr<T>>::Create(num_entries, entries, nullptr);
219 gpr_free(entries);
220 }
221 return method_config_table;
222 }
223
224 template <typename T>
MethodConfigTableLookup(const SliceHashTable<RefCountedPtr<T>> & table,grpc_slice path)225 RefCountedPtr<T> ServiceConfig::MethodConfigTableLookup(
226 const SliceHashTable<RefCountedPtr<T>>& table, grpc_slice path) {
227 const RefCountedPtr<T>* value = table.Get(path);
228 // If we didn't find a match for the path, try looking for a wildcard
229 // entry (i.e., change "/service/method" to "/service/*").
230 if (value == nullptr) {
231 char* path_str = grpc_slice_to_c_string(path);
232 const char* sep = strrchr(path_str, '/') + 1;
233 const size_t len = (size_t)(sep - path_str);
234 char* buf = (char*)gpr_malloc(len + 2); // '*' and NUL
235 memcpy(buf, path_str, len);
236 buf[len] = '*';
237 buf[len + 1] = '\0';
238 grpc_slice wildcard_path = grpc_slice_from_copied_string(buf);
239 gpr_free(buf);
240 value = table.Get(wildcard_path);
241 grpc_slice_unref_internal(wildcard_path);
242 gpr_free(path_str);
243 }
244 return RefCountedPtr<T>(*value);
245 }
246
247 } // namespace grpc_core
248
249 #endif /* GRPC_CORE_LIB_TRANSPORT_SERVICE_CONFIG_H */
250