VLink  2.1.0
A high-performance communication middleware
client-inl.h
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 #pragma once
25 
26 #include <memory>
27 #include <string>
28 #include <utility>
29 
30 #include "../base/cpu_profiler_guard.h"
31 #include "../base/logger.h"
32 #include "../base/memory_resource.h"
33 #include "../client.h"
34 #include "../impl/url.h"
35 #include "../serializer.h"
36 
37 namespace vlink {
38 
39 template <typename ReqT, typename RespT, SecurityType SecT>
41  const std::string& url_str, InitType type) {
42  return std::make_unique<Client<ReqT, RespT, SecT>>(url_str, type);
43 }
44 
45 template <typename ReqT, typename RespT, SecurityType SecT>
47  const std::string& url_str, InitType type) {
48  return std::make_shared<Client<ReqT, RespT, SecT>>(url_str, type);
49 }
50 
51 template <typename ReqT, typename RespT, SecurityType SecT>
52 template <typename ConfT, typename>
53 inline Client<ReqT, RespT, SecT>::Client(const ConfT& conf, InitType type) {
54  static_assert(ConfT::get_allow_impl_type() & kImplType, "Conf does not support client mode.");
55 
56  if VUNLIKELY (!conf.parse(kImplType) || !conf.is_valid()) {
57  VLOG_F(conf, " client configuration is invalid or could not be parsed.");
58  return;
59  }
60 
61  this->impl_ = conf.create_client();
62 
63  if VUNLIKELY (!this->impl_) {
64  VLOG_F(conf, " client implementation not available for this transport.");
65  return;
66  }
67 
68  this->impl_->transport_type = conf.get_transport_type();
69  this->impl_->ser_type = Serializer::get_serialized_type<kReqType, ReqT>();
70 
71  if constexpr (kHasResp) {
72  const auto resp_ser_type = Serializer::get_serialized_type<kRespType, RespT>();
73 
74  if (!this->impl_->ser_type.empty() || !resp_ser_type.empty()) {
75  this->impl_->ser_type += "|" + resp_ser_type;
76  }
77  }
78 
79  {
80  constexpr auto kReqSchemaType = Serializer::get_schema_type<kReqType, ReqT>();
81  constexpr auto kRespSchemaType = Serializer::get_schema_type<kRespType, RespT>();
82 
83  if constexpr (kHasResp && kReqSchemaType != kRespSchemaType) {
84  this->impl_->schema_type = SchemaType::kUnknown;
85  } else {
86  this->impl_->schema_type = kReqSchemaType;
87  }
88  }
89 
90  this->impl_->is_cdr_type = Serializer::is_cdr_type<ReqT>();
91  this->impl_->is_resp_type = kHasResp;
92 
93  if constexpr (kHasResp) {
94  this->impl_->is_resp_cdr_type = Serializer::is_cdr_type<RespT>();
95  }
96 
97  if constexpr (std::is_same_v<ConfT, Url>) {
98  this->impl_->url = conf.get_str();
99  }
100 
101  if constexpr (SecT == SecurityType::kWithSecurity) {
102  this->impl_->is_security_type = true;
103  }
104 
105  if VLIKELY (type == InitType::kWithInit) {
106  this->init(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall)
107  }
108 }
109 
110 template <typename ReqT, typename RespT, SecurityType SecT>
111 inline Client<ReqT, RespT, SecT>::Client(const std::string& url_str, InitType type)
112  : Client<ReqT, RespT, SecT>(Url(url_str), type) {}
113 
114 template <typename ReqT, typename RespT, SecurityType SecT>
116  {
117  std::lock_guard lock(future_mtx_);
118  future_map_.clear();
119  }
120 
121  // NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.VirtualCall)
122  this->deinit();
123 }
124 
125 template <typename ReqT, typename RespT, SecurityType SecT>
127  this->impl_->detect_connected(std::move(callback));
128 }
129 
130 template <typename ReqT, typename RespT, SecurityType SecT>
131 inline bool Client<ReqT, RespT, SecT>::wait_for_connected(std::chrono::milliseconds timeout) {
132  if VUNLIKELY (timeout.count() == 0) {
133  VLOG_W("Client: Timeout value is 0, using infinite wait instead.");
134  timeout = Timeout::kInfinite;
135  }
136 
137  return this->impl_->wait_for_connected(timeout);
138 }
139 
140 template <typename ReqT, typename RespT, SecurityType SecT>
142  return this->impl_->is_connected();
143 }
144 
145 template <typename ReqT, typename RespT, SecurityType SecT>
146 inline bool Client<ReqT, RespT, SecT>::invoke(const ReqT& req, RespT& resp, std::chrono::milliseconds timeout) {
147  if VUNLIKELY (timeout.count() == 0) {
148  VLOG_W("Client: Timeout value is 0, using infinite wait instead.");
149  timeout = Timeout::kInfinite;
150  }
151 
152 #ifndef VLINK_DISABLE_PROFILER
153  CpuProfilerGuard profiler_guard(this->impl_->profiler.get());
154 #endif
155 
156  static_assert(kHasResp, "Invoke requires a response type.");
157 
158  bool ret = false;
159 
160  if constexpr (std::is_same_v<ReqT, Bytes> && std::is_same_v<RespT, Bytes>) {
161  ret = call_bytes(req, [&resp](const Bytes& resp_data) { resp = resp_data; }, timeout);
162  } else {
163  Bytes req_data;
164  const bool use_loan = SecT != SecurityType::kWithSecurity && this->is_support_loan_;
165 
166  if VUNLIKELY (!Serializer::serialize_to_transport<kReqType>(
167  req, req_data, this->impl_->transport_type, use_loan,
168  [this](size_t size) { return this->impl_->loan(size); })) {
169  VLOG_T("Client serialize failed, url: ", this->impl_->url, ".");
170 
171  if constexpr (SecT != SecurityType::kWithSecurity) {
172  if (this->is_support_loan_) {
173  this->impl_->return_loan(req_data);
174  }
175  }
176 
177  return false;
178  }
179 
180  bool deserialize_success = false;
181 
182  ret = call_bytes(
183  req_data,
184  [this, &resp, &deserialize_success](const Bytes& resp_data) {
185  if VLIKELY (Serializer::deserialize<kRespType>(resp_data, resp, this->impl_->transport_type)) {
186  deserialize_success = true;
187  } else {
188  VLOG_T("Client deserialize failed, url: ", this->impl_->url, ".");
189  }
190  },
191  timeout);
192 
193  ret = ret && deserialize_success;
194  }
195 
196  return ret;
197 }
198 
199 template <typename ReqT, typename RespT, SecurityType SecT>
200 inline std::optional<RespT> Client<ReqT, RespT, SecT>::invoke(const ReqT& req, std::chrono::milliseconds timeout) {
201  if VUNLIKELY (timeout.count() == 0) {
202  VLOG_W("Client: Timeout value is 0, using infinite wait instead.");
203  timeout = Timeout::kInfinite;
204  }
205 
206 #ifndef VLINK_DISABLE_PROFILER
207  CpuProfilerGuard profiler_guard(this->impl_->profiler.get());
208 #endif
209 
210  auto resp = this->template get_default_value<RespT>();
211 
212  if VLIKELY (invoke(req, resp, timeout)) {
213  return std::make_optional<RespT>(std::move(resp));
214  }
215 
216  return std::nullopt;
217 }
218 
219 template <typename ReqT, typename RespT, SecurityType SecT>
220 inline bool Client<ReqT, RespT, SecT>::invoke(const ReqT& req, RespCallback&& callback) {
221 #ifndef VLINK_DISABLE_PROFILER
222  CpuProfilerGuard profiler_guard(this->impl_->profiler.get());
223 #endif
224 
225  static_assert(kHasResp, "Invoke requires a response type.");
226 
227  bool ret = false;
228 
229  if constexpr (std::is_same_v<ReqT, Bytes> && std::is_same_v<RespT, Bytes>) {
230  ret = call_bytes(req, [callback = std::move(callback)](const Bytes& resp_data) { callback(resp_data); });
231  } else {
232  Bytes req_data;
233  const bool use_loan = SecT != SecurityType::kWithSecurity && this->is_support_loan_;
234 
235  if VUNLIKELY (!Serializer::serialize_to_transport<kReqType>(
236  req, req_data, this->impl_->transport_type, use_loan,
237  [this](size_t size) { return this->impl_->loan(size); })) {
238  VLOG_T("Client serialize failed, url: ", this->impl_->url, ".");
239 
240  if constexpr (SecT != SecurityType::kWithSecurity) {
241  if (this->is_support_loan_) {
242  this->impl_->return_loan(req_data);
243  }
244  }
245 
246  return false;
247  }
248 
249  ret = call_bytes(req_data, [this, callback = std::move(callback)](const Bytes& resp_data) {
250  auto resp = this->template get_default_value<RespT>();
251 
252  if VUNLIKELY (!Serializer::deserialize<kRespType>(resp_data, resp, this->impl_->transport_type)) {
253  VLOG_T("Client deserialize failed, url: ", this->impl_->url, ".");
254  return;
255  }
256 
257  callback(resp);
258  });
259  }
260 
261  return ret;
262 }
263 
264 template <typename ReqT, typename RespT, SecurityType SecT>
265 inline std::future<RespT> Client<ReqT, RespT, SecT>::async_invoke(const ReqT& req) {
266 #ifndef VLINK_DISABLE_PROFILER
267  CpuProfilerGuard profiler_guard(this->impl_->profiler.get());
268 #endif
269 
270  static_assert(kHasResp, "async_invoke requires a response type.");
271 
272  auto pro = MemoryResource::make_shared<std::promise<RespT>>();
273  auto future = pro->get_future();
274 
275  bool ret = false;
276  int64_t target_seq = 0;
277 
278  {
279  std::lock_guard lock(future_mtx_);
280  target_seq = future_seq_++;
281  future_map_.emplace(target_seq, pro);
282  }
283 
284  auto cleanup_on_error = [this, target_seq, pro](const std::string& error_str) {
285  std::lock_guard lock(future_mtx_);
286  future_map_.erase(target_seq);
287 
288  try {
289  throw Exception::RuntimeError(error_str);
290  } catch (std::exception&) {
291  pro->set_exception(std::current_exception());
292  }
293  };
294 
295  if constexpr (std::is_same_v<ReqT, Bytes> && std::is_same_v<RespT, Bytes>) {
296  ret = call_bytes(req, [this, target_seq](const Bytes& resp_data) {
297  std::lock_guard lock(future_mtx_);
298  auto it = future_map_.find(target_seq);
299 
300  if VLIKELY (it != future_map_.end()) {
301  it->second->set_value(resp_data);
302  future_map_.erase(it);
303  }
304  });
305  } else {
306  Bytes req_data;
307  const bool use_loan = SecT != SecurityType::kWithSecurity && this->is_support_loan_;
308 
309  if VUNLIKELY (!Serializer::serialize_to_transport<kReqType>(
310  req, req_data, this->impl_->transport_type, use_loan,
311  [this](size_t size) { return this->impl_->loan(size); })) {
312  VLOG_T("Client serialize failed, url: ", this->impl_->url, ".");
313 
314  if constexpr (SecT != SecurityType::kWithSecurity) {
315  if (this->is_support_loan_) {
316  this->impl_->return_loan(req_data);
317  }
318  }
319 
320  cleanup_on_error("Client async_invoke error (Failed to serialize req)");
321  return future;
322  }
323 
324  ret = call_bytes(req_data, [this, target_seq](const Bytes& resp_data) {
325  bool convert_success = false;
326 
327  auto resp = this->template get_default_value<RespT>();
328 
329  if VLIKELY (Serializer::deserialize<kRespType>(resp_data, resp, this->impl_->transport_type)) {
330  convert_success = true;
331  } else {
332  VLOG_T("Client deserialize failed, url: ", this->impl_->url, ".");
333  }
334 
335  std::lock_guard lock(future_mtx_);
336 
337  auto it = future_map_.find(target_seq);
338 
339  if VLIKELY (it != future_map_.end()) {
340  if VLIKELY (convert_success) {
341  it->second->set_value(std::move(resp));
342  } else {
343  try {
344  throw Exception::RuntimeError("Client async_invoke error (Failed to deserialize resp)");
345  } catch (std::exception&) {
346  it->second->set_exception(std::current_exception());
347  }
348  }
349 
350  future_map_.erase(it);
351  }
352  });
353  }
354 
355  if VUNLIKELY (!ret) {
356  cleanup_on_error("Client async_invoke error (Failed to call)");
357  }
358 
359  return future;
360 }
361 
362 template <typename ReqT, typename RespT, SecurityType SecT>
363 inline bool Client<ReqT, RespT, SecT>::send(const ReqT& req) {
364 #ifndef VLINK_DISABLE_PROFILER
365  CpuProfilerGuard profiler_guard(this->impl_->profiler.get());
366 #endif
367 
368  static_assert(!kHasResp, "Send not supported; use invoke() for request-response.");
369 
370  bool ret = false;
371 
372  if constexpr (std::is_same_v<ReqT, Bytes>) {
373  ret = call_bytes(req);
374  } else {
375  Bytes req_data;
376  const bool use_loan = SecT != SecurityType::kWithSecurity && this->is_support_loan_;
377 
378  if VUNLIKELY (!Serializer::serialize_to_transport<kReqType>(
379  req, req_data, this->impl_->transport_type, use_loan,
380  [this](size_t size) { return this->impl_->loan(size); })) {
381  VLOG_T("Client serialize failed, url: ", this->impl_->url, ".");
382 
383  if constexpr (SecT != SecurityType::kWithSecurity) {
384  if (this->is_support_loan_) {
385  this->impl_->return_loan(req_data);
386  }
387  }
388 
389  return false;
390  }
391 
392  ret = call_bytes(req_data);
393  }
394 
395  return ret;
396 }
397 
398 template <typename ReqT, typename RespT, SecurityType SecT>
399 inline bool Client<ReqT, RespT, SecT>::call_bytes(const Bytes& req_data, NodeImpl::MsgCallback&& callback,
400  std::chrono::milliseconds timeout) {
401  if constexpr (SecT == SecurityType::kWithSecurity) {
402  Bytes req_sec_data;
403 
404  if VUNLIKELY (!this->impl_->security || !this->impl_->security->encrypt(req_data, req_sec_data)) {
405  VLOG_T("Client encrypt failed, url: ", this->impl_->url, ".");
406  return false;
407  }
408 
409  if VUNLIKELY (!callback) {
410  return this->impl_->call(req_sec_data, nullptr, timeout);
411  }
412 
413  return this->impl_->call(
414  req_sec_data,
415  [this, callback = std::move(callback)](const Bytes& resp_data) {
416  Bytes resp_sec_data;
417 
418  if VUNLIKELY (!this->impl_->security || !this->impl_->security->decrypt(resp_data, resp_sec_data)) {
419  VLOG_T("Client decrypt failed, url: ", this->impl_->url, ".");
420  return;
421  }
422 
423  this->invoke_callback(callback, resp_sec_data);
424  },
425  timeout);
426  } else {
427  this->impl_->try_record(ActionType::kClientRequest, req_data);
428 
429  if VUNLIKELY (!callback) {
430  return this->impl_->call(req_data, nullptr, timeout);
431  }
432 
433  return this->impl_->call(
434  req_data,
435  [this, callback = std::move(callback)](const Bytes& resp_data) {
436  this->impl_->try_record(ActionType::kClientResponse, resp_data);
437 
438  this->invoke_callback(callback, resp_data);
439  },
440  timeout);
441  }
442 }
443 
444 template <typename ReqT, typename RespT>
445 template <typename SecurityConfigT>
447  const std::string& url_str, SecurityConfigT&& sec_cfg, InitType type) {
448  static_assert(std::is_same_v<std::decay_t<SecurityConfigT>, Security::Config>,
449  "SecurityConfigT must be Security::Config.");
450 
451  return std::make_unique<SecurityClient<ReqT, RespT>>(url_str, std::forward<SecurityConfigT>(sec_cfg), type);
452 }
453 
454 template <typename ReqT, typename RespT>
455 template <typename SecurityConfigT>
457  const std::string& url_str, SecurityConfigT&& sec_cfg, InitType type) {
458  static_assert(std::is_same_v<std::decay_t<SecurityConfigT>, Security::Config>,
459  "SecurityConfigT must be Security::Config.");
460 
461  return std::make_shared<SecurityClient<ReqT, RespT>>(url_str, std::forward<SecurityConfigT>(sec_cfg), type);
462 }
463 
464 template <typename ReqT, typename RespT>
465 template <typename ConfT, typename SecurityConfigT, typename>
466 inline SecurityClient<ReqT, RespT>::SecurityClient(const ConfT& conf, SecurityConfigT&& sec_cfg, InitType type)
467  : Client<ReqT, RespT, SecurityType::kWithSecurity>(conf, InitType::kWithoutInit) {
468  static_assert(std::is_same_v<std::decay_t<SecurityConfigT>, Security::Config>,
469  "SecurityConfigT must be Security::Config.");
470 
471  this->enable_security(std::forward<SecurityConfigT>(sec_cfg));
472 
473  if VLIKELY (type == InitType::kWithInit) {
474  this->init(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall)
475  }
476 }
477 
478 template <typename ReqT, typename RespT>
479 template <typename SecurityConfigT>
480 inline SecurityClient<ReqT, RespT>::SecurityClient(const std::string& url_str, SecurityConfigT&& sec_cfg, InitType type)
481  : Client<ReqT, RespT, SecurityType::kWithSecurity>(url_str, InitType::kWithoutInit) {
482  static_assert(std::is_same_v<std::decay_t<SecurityConfigT>, Security::Config>,
483  "SecurityConfigT must be Security::Config.");
484 
485  this->enable_security(std::forward<SecurityConfigT>(sec_cfg));
486 
487  if VLIKELY (type == InitType::kWithInit) {
488  this->init(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall)
489  }
490 }
491 
492 } // namespace vlink
#define VLOG_F(...)
Definition: logger.h:843
#define VLOG_W(...)
Definition: logger.h:839
#define VLOG_T(...)
Definition: logger.h:833
#define VUNLIKELY(...)
Short alias for VLINK_UNLIKELY.
Definition: macros.h:289
#define VLIKELY(...)
Short alias for VLINK_LIKELY.
Definition: macros.h:284