/src/trafficserver/include/mgmt/rpc/jsonrpc/JsonRPCManager.h
Line | Count | Source |
1 | | /** |
2 | | @section license License |
3 | | |
4 | | Licensed to the Apache Software Foundation (ASF) under one |
5 | | or more contributor license agreements. See the NOTICE file |
6 | | distributed with this work for additional information |
7 | | regarding copyright ownership. The ASF licenses this file |
8 | | to you under the Apache License, Version 2.0 (the |
9 | | "License"); you may not use this file except in compliance |
10 | | with the License. You may obtain a copy of the License at |
11 | | |
12 | | http://www.apache.org/licenses/LICENSE-2.0 |
13 | | |
14 | | Unless required by applicable law or agreed to in writing, software |
15 | | distributed under the License is distributed on an "AS IS" BASIS, |
16 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
17 | | See the License for the specific language governing permissions and |
18 | | limitations under the License. |
19 | | */ |
20 | | #pragma once |
21 | | |
22 | | #include <iostream> |
23 | | #include <map> |
24 | | #include <functional> |
25 | | #include <tuple> |
26 | | #include <forward_list> |
27 | | #include <string_view> |
28 | | #include <variant> |
29 | | #include <mutex> |
30 | | |
31 | | #include "tscore/Diags.h" |
32 | | #include "ts/apidefs.h" |
33 | | |
34 | | #include "mgmt/rpc/jsonrpc/Defs.h" |
35 | | #include "mgmt/rpc/jsonrpc/Context.h" |
36 | | |
37 | | namespace rpc |
38 | | { |
39 | | // forward |
40 | | namespace json_codecs |
41 | | { |
42 | | class yamlcpp_json_decoder; |
43 | | class yamlcpp_json_encoder; |
44 | | } // namespace json_codecs |
45 | | |
46 | | /// |
47 | | /// @brief This class keeps all relevant @c RPC provider's info. |
48 | | /// |
49 | | struct RPCRegistryInfo { |
50 | | std::string_view provider; ///< Who's the rpc endpoint provider, could be ATS or a plugins. When requesting the service info from |
51 | | ///< the rpc node, this will be part of the service info. |
52 | | }; |
53 | | |
54 | | /// |
55 | | /// @brief JSONRPC registration and JSONRPC invocation logic https://www.jsonrpc.org/specification |
56 | | /// doc TBC |
57 | | class JsonRPCManager |
58 | | { |
59 | | private: |
60 | | /// @note In case we want to change the codecs and use another library, we just need to follow the same signatures @see |
61 | | /// yamlcpp_json_decoder and @see yamlcpp_json_encoder. |
62 | | // We use the yamlcpp library by default. |
63 | | using Decoder = json_codecs::yamlcpp_json_decoder; |
64 | | using Encoder = json_codecs::yamlcpp_json_encoder; |
65 | | |
66 | | public: |
67 | | // Possible RPC method signatures. |
68 | | using MethodHandlerSignature = std::function<swoc::Rv<YAML::Node>(std::string_view const &, const YAML::Node &)>; |
69 | | using PluginMethodHandlerSignature = std::function<void(std::string_view const &, const YAML::Node &)>; |
70 | | using NotificationHandlerSignature = std::function<void(const YAML::Node &)>; |
71 | | |
72 | | /// |
73 | | /// @brief Add new registered method handler to the JSON RPC engine. |
74 | | /// |
75 | | /// @tparam Func The callback function type. See @c MethodHandlerSignature |
76 | | /// @param name Name to be exposed by the RPC Engine, this should match the incoming request. i.e: If you register 'get_stats' |
77 | | /// then the incoming jsonrpc call should have this very same name in the 'method' field. .. {...'method': |
78 | | /// 'get_stats'...} . |
79 | | /// @param call The function handler. |
80 | | /// @param info RPCRegistryInfo pointer. |
81 | | /// @param opt Handler options, used to pass information about the registered handler. |
82 | | /// @return bool Boolean flag. true if the callback was successfully added, false otherwise |
83 | | /// |
84 | | template <typename Func> |
85 | | bool add_method_handler(std::string_view name, Func &&call, const RPCRegistryInfo *info, TSRPCHandlerOptions const &opt); |
86 | | |
87 | | /// |
88 | | /// @brief Add new registered method handler(from a plugin scope) to the JSON RPC engine. |
89 | | /// |
90 | | /// Function to add a new RPC handler from a plugin context. This will be invoked by @c TSRPCRegisterMethodHandler. If you |
91 | | /// register your handler by using this API then you have to express the result of the processing by either calling |
92 | | /// @c TSInternalHandlerDone or @c TSInternalHandlerError in case of an error. |
93 | | /// When a function registered by this mechanism gets called, the response from the handler will not matter, instead we will rely |
94 | | /// on what the @c TSInternalHandlerDone or @c TSInternalHandlerError have set. |
95 | | /// |
96 | | /// @note If you are not a plugin, do not call this function. Use @c add_method_handler instead. |
97 | | /// |
98 | | /// @tparam Func The callback function type. See @c PluginMethodHandlerSignature |
99 | | /// @param name Name to be exposed by the RPC Engine, this should match the incoming request. i.e: If you register 'get_stats' |
100 | | /// then the incoming jsonrpc call should have this very same name in the 'method' field. .. {...'method': |
101 | | /// 'get_stats'...} . |
102 | | /// @param call The function handler. |
103 | | /// @param info RPCRegistryInfo pointer. |
104 | | /// @param opt Handler options, used to pass information about the registered handler. |
105 | | /// @return bool Boolean flag. true if the callback was successfully added, false otherwise |
106 | | /// |
107 | | template <typename Func> |
108 | | bool add_method_handler_from_plugin(const std::string &name, Func &&call, const RPCRegistryInfo *info, |
109 | | TSRPCHandlerOptions const &opt); |
110 | | |
111 | | /// |
112 | | /// @brief Add new registered notification handler to the JSON RPC engine. |
113 | | /// |
114 | | /// @tparam Func The callback function type. See @c NotificationHandlerSignature |
115 | | /// @param name Name to be exposed by the RPC Engine. |
116 | | /// @param call The callback function that needs handler. |
117 | | /// @param info RPCRegistryInfo pointer. |
118 | | /// @param opt Handler options, used to pass information about the registered handler. |
119 | | /// @return bool Boolean flag. true if the callback was successfully added, false otherwise |
120 | | /// |
121 | | template <typename Func> |
122 | | bool add_notification_handler(std::string_view name, Func &&call, const RPCRegistryInfo *info, TSRPCHandlerOptions const &opt); |
123 | | |
124 | | /// |
125 | | /// @brief This function handles the incoming jsonrpc request and dispatch the associated registered handler. |
126 | | /// |
127 | | /// @param ctx @c Context object used pass information between rpc layers. |
128 | | /// @param jsonString The incoming jsonrpc 2.0 message. \link https://www.jsonrpc.org/specification |
129 | | /// @return std::optional<std::string> For methods, a valid jsonrpc 2.0 json string will be passed back. Notifications will not |
130 | | /// contain any json back. |
131 | | /// |
132 | | std::optional<std::string> handle_call(Context const &ctx, std::string const &jsonString); |
133 | | |
134 | | /// |
135 | | /// @brief Get the instance of the whole RPC engine. |
136 | | /// |
137 | | /// @return JsonRPCManager& The JsonRPCManager protocol implementation object. |
138 | | /// |
139 | | static JsonRPCManager & |
140 | | instance() |
141 | 0 | { |
142 | 0 | static JsonRPCManager rpc; |
143 | 0 | return rpc; |
144 | 0 | } |
145 | | |
146 | | protected: // For unit test. |
147 | 5.81k | JsonRPCManager() = default; |
148 | | JsonRPCManager(JsonRPCManager const &) = delete; |
149 | | JsonRPCManager(JsonRPCManager &&) = delete; |
150 | | JsonRPCManager &operator=(JsonRPCManager const &) = delete; |
151 | | JsonRPCManager &operator=(JsonRPCManager &&) = delete; |
152 | | |
153 | | /// |
154 | | /// @brief Remove handler from the registered method handlers. Test only. |
155 | | /// |
156 | | /// @param name Method name. |
157 | | /// @return true If all is good. |
158 | | /// @return false If we could not remove it. |
159 | | /// |
160 | | bool remove_handler(std::string_view name); |
161 | | friend bool test_remove_handler(std::string_view name); |
162 | | |
163 | | private: |
164 | | /// |
165 | | /// @brief Internal class that holds and handles the dispatch logic. |
166 | | /// |
167 | | /// It holds methods and notifications as well as provides the mechanism to call each particular handler. |
168 | | /// |
169 | | /// Design notes: |
170 | | /// |
171 | | /// This class holds a std::unordered_map<std::string, InternalHandler> as a main table for all the callbacks. |
172 | | /// The @c InternalHandler wraps a std::variant with the supported handler types, depending on each handler type the invocation |
173 | | /// varies. All handlers gets call synchronously with the difference that for Plugin handlers (see @c PluginMethod) we will wait |
174 | | /// for the response to be set, plugins are provided with an API to deal with different responses(success or error), plugins do |
175 | | /// not require to respond to the callback with a response, see @c PluginMethodHandlerSignature . |
176 | | /// @c FunctionWrapper class holds the actual @c std::function<T> object, this class is needed to make easy to handle equal |
177 | | /// signatures inside a @c std::variant |
178 | | class Dispatcher |
179 | | { |
180 | | /// The response type used internally, notifications won't fill in the optional response. Internal response's @ ec will be set |
181 | | /// in case of any error. |
182 | | using response_type = std::optional<specs::RPCResponseInfo>; |
183 | | |
184 | | /// |
185 | | /// @brief Class that wraps the actual std::function<T>. |
186 | | /// |
187 | | /// @tparam T Handler signature See @c MethodHandlerSignature @c PluginMethodHandlerSignature @c NotificationHandlerSignature |
188 | | /// |
189 | | template <typename T> struct FunctionWrapper { |
190 | 11.6k | FunctionWrapper(T &&t) : cb(std::forward<T>(t)) {} |
191 | | |
192 | | T cb; ///< Function handler (std::function<T>) |
193 | | }; |
194 | | |
195 | | struct InternalHandler; ///< fw declaration |
196 | | /// To avoid long lines. |
197 | | using InternalHandlers = std::unordered_map<std::string, InternalHandler>; |
198 | | |
199 | | public: |
200 | | Dispatcher(); |
201 | | /// Add a method handler to the internal container |
202 | | /// @return True if was successfully added, False otherwise. |
203 | | template <typename FunctionWrapperType, typename Handler> |
204 | | bool add_handler(std::string_view name, Handler &&handler, const RPCRegistryInfo *info, TSRPCHandlerOptions const &opt); |
205 | | |
206 | | /// Find and call the request's callback. If any error occurs, the return type will have the specific error. |
207 | | /// For notifications the @c RPCResponseInfo will not be set as part of the response. @c response_type |
208 | | response_type dispatch(Context const &ctx, specs::RPCRequestInfo const &request) const; |
209 | | |
210 | | /// Find a particular registered handler(method) by its associated name. |
211 | | /// @return A pair. The handler itself and a boolean flag indicating that the handler was found. If not found, second will |
212 | | /// be false and the handler null. |
213 | | InternalHandler const &find_handler(specs::RPCRequestInfo const &request, std::error_code &ec) const; |
214 | | /// Removes a method handler. Unit test mainly. |
215 | | bool remove_handler(std::string_view name); |
216 | | |
217 | | // JSONRPC API - here for now. |
218 | | swoc::Rv<YAML::Node> show_registered_handlers(std::string_view const &, const YAML::Node &); |
219 | | swoc::Rv<YAML::Node> get_service_descriptor(std::string_view const &, const YAML::Node &); |
220 | | |
221 | | // Supported handler endpoint types. |
222 | | using Method = FunctionWrapper<MethodHandlerSignature>; |
223 | | using PluginMethod = FunctionWrapper<PluginMethodHandlerSignature>; |
224 | | // Plugins and non plugins handlers have no difference from the point of view of the RPC manager, we call and we do not expect |
225 | | // for the work to be finished. Notifications have no response at all. |
226 | | using Notification = FunctionWrapper<NotificationHandlerSignature>; |
227 | | |
228 | | private: |
229 | | /// Register our own api into the RPC manager. This will expose the methods and notifications registered in the RPC |
230 | | void register_service_descriptor_handler(); |
231 | | |
232 | | // Functions to deal with the handler invocation. |
233 | | response_type invoke_method_handler(InternalHandler const &handler, specs::RPCRequestInfo const &request) const; |
234 | | response_type invoke_notification_handler(InternalHandler const &handler, specs::RPCRequestInfo const &request) const; |
235 | | |
236 | | /// |
237 | | /// @brief Class that wraps callable objects of any RPC specific type. If provided, this class also holds a valid registry |
238 | | /// information |
239 | | /// |
240 | | /// This class holds the actual callable object from one of our supported types, this helps us to |
241 | | /// simplify the logic to insert and fetch callable objects from our container. |
242 | | struct InternalHandler { |
243 | | InternalHandler() = default; |
244 | 11.6k | InternalHandler(const RPCRegistryInfo *info, TSRPCHandlerOptions const &opt) : _regInfo(info), _options(opt) {} |
245 | | /// Sets the handler. |
246 | | template <class T, class F> void set_callback(F &&t); |
247 | | explicit operator bool() const; |
248 | | bool operator!() const; |
249 | | /// Invoke the actual handler callback. |
250 | | swoc::Rv<YAML::Node> invoke(specs::RPCRequestInfo const &request) const; |
251 | | /// Check if the handler was registered as method. |
252 | | bool is_method() const; |
253 | | |
254 | | /// Returns the internal registry info. |
255 | | const RPCRegistryInfo * |
256 | | get_reg_info() const |
257 | 0 | { |
258 | 0 | return _regInfo; |
259 | 0 | } |
260 | | |
261 | | /// Returns the configured options associated with this particular handler. |
262 | | TSRPCHandlerOptions const & |
263 | | get_options() const |
264 | 0 | { |
265 | 0 | return _options; |
266 | 0 | } |
267 | | |
268 | | private: |
269 | | // We need to keep this match with the order of types in the _func variant. This will help us to identify the holding type. |
270 | | enum class VariantTypeIndexId : std::size_t { NOTIFICATION = 1, METHOD = 2, METHOD_FROM_PLUGIN = 3 }; |
271 | | // We support these three for now. This can easily be extended to support other signatures. |
272 | | // that's one of the main points of the InternalHandler |
273 | | std::variant<std::monostate, Notification, Method, PluginMethod> _func; |
274 | | const RPCRegistryInfo *_regInfo = |
275 | | nullptr; ///< Can hold internal information about the handler, this could be null as it is optional. |
276 | | ///< This pointer can eventually holds important information about the call. |
277 | | TSRPCHandlerOptions _options; |
278 | | }; |
279 | | // We will keep all the handlers wrapped inside the InternalHandler class, this will help us |
280 | | // to have a single container for all the types(method, notification & plugin method(cond var)). |
281 | | InternalHandlers _handlers; ///< Registered handler container. |
282 | | mutable std::mutex _mutex; ///< insert/find/delete mutex. |
283 | | }; |
284 | | |
285 | | Dispatcher _dispatcher; ///< Internal handler container and dispatcher logic object. |
286 | | }; |
287 | | |
288 | | // ------------------------------ JsonRPCManager ------------------------------- |
289 | | template <typename Handler> |
290 | | bool |
291 | | JsonRPCManager::add_method_handler(std::string_view name, Handler &&call, const RPCRegistryInfo *info, |
292 | | TSRPCHandlerOptions const &opt) |
293 | | { |
294 | | return _dispatcher.add_handler<Dispatcher::Method, Handler>(name, std::forward<Handler>(call), info, opt); |
295 | | } |
296 | | template <typename Handler> |
297 | | bool |
298 | | JsonRPCManager::add_method_handler_from_plugin(const std::string &name, Handler &&call, const RPCRegistryInfo *info, |
299 | | TSRPCHandlerOptions const &opt) |
300 | | { |
301 | | return _dispatcher.add_handler<Dispatcher::PluginMethod, Handler>(name, std::forward<Handler>(call), info, opt); |
302 | | } |
303 | | |
304 | | template <typename Handler> |
305 | | bool |
306 | | JsonRPCManager::add_notification_handler(std::string_view name, Handler &&call, const RPCRegistryInfo *info, |
307 | | TSRPCHandlerOptions const &opt) |
308 | | { |
309 | | return _dispatcher.add_handler<Dispatcher::Notification, Handler>(name, std::forward<Handler>(call), info, opt); |
310 | | } |
311 | | |
312 | | // ----------------------------- InternalHandler ------------------------------- |
313 | | template <class T, class F> |
314 | | void |
315 | | JsonRPCManager::Dispatcher::InternalHandler::set_callback(F &&f) |
316 | 11.6k | { |
317 | | // T would be one of the handler endpoint types. |
318 | 11.6k | _func = T{std::forward<F>(f)}; |
319 | 11.6k | } |
320 | | inline JsonRPCManager::Dispatcher::InternalHandler::operator bool() const |
321 | 0 | { |
322 | 0 | return _func.index() != 0; |
323 | 0 | } |
324 | | bool inline JsonRPCManager::Dispatcher::InternalHandler::operator!() const |
325 | 0 | { |
326 | 0 | return _func.index() == 0; |
327 | 0 | } |
328 | | |
329 | | // ----------------------------- Dispatcher ------------------------------------ |
330 | | template <typename FunctionWrapperType, typename Handler> |
331 | | bool |
332 | | JsonRPCManager::Dispatcher::add_handler(std::string_view name, Handler &&handler, const RPCRegistryInfo *info, |
333 | | TSRPCHandlerOptions const &opt) |
334 | 11.6k | { |
335 | 11.6k | std::lock_guard<std::mutex> lock(_mutex); |
336 | 11.6k | InternalHandler call{info, opt}; |
337 | 11.6k | call.set_callback<FunctionWrapperType>(std::forward<Handler>(handler)); |
338 | 11.6k | return _handlers.emplace(name, std::move(call)).second; |
339 | 11.6k | } |
340 | | |
341 | | } // namespace rpc |