Coverage Report

Created: 2026-02-10 06:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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