266 lines
8.6 KiB
C++
266 lines
8.6 KiB
C++
#include "ApiClientInterop.h"
|
|
#include "lib/quickbus/quickbus.h"
|
|
#include "lib/config/config.h"
|
|
#include "lib/logging/logging.h"
|
|
#include "lib/utils/bts_assert.h"
|
|
#include "lib/utils/Dispatcher.h"
|
|
|
|
#include "../public/ServiceContext.h"
|
|
#include "../public/Result.h"
|
|
|
|
#include "lib/api/Apicore.pb.h"
|
|
#include "lib/config/protos/quickbus_cfg.pb.h"
|
|
#include "lib/config/protos/system.pb.h"
|
|
#include "lib/config/protos/Ports.pb.h"
|
|
|
|
#ifdef WIN32
|
|
#include <excpt.h>
|
|
#endif
|
|
|
|
namespace bts {
|
|
namespace api {
|
|
|
|
config::QuickbusNodeCfg get_client_node_cfg()
|
|
{
|
|
auto cfg = config::get<config::QuickbusCfg>();
|
|
config::QuickbusNodeCfg node_cfg;
|
|
node_cfg.set_reconnect_period_secs(cfg.reconnect_period_secs());
|
|
node_cfg.set_hb_period_msecs(cfg.hb_period_msecs());
|
|
node_cfg.set_max_missed_hbs(cfg.max_missed_hbs());
|
|
node_cfg.set_parent_host(
|
|
cfg.has_qbus_host() ?
|
|
cfg.qbus_host() :
|
|
config::get<config::System>().config_ip()
|
|
);
|
|
node_cfg.set_parent_port(
|
|
cfg.has_qbus_port() ?
|
|
cfg.qbus_port() :
|
|
config::get<config::Ports>().qbus_port()
|
|
);
|
|
node_cfg.set_my_port(0);
|
|
return node_cfg;
|
|
}
|
|
|
|
//
|
|
// Common
|
|
|
|
CAPI Quickbus* api_create_quickbus(const char* qbconfig, long length) {
|
|
// If config hasn't been initialized yet do it now and suppress exceptions.
|
|
// When we're called from a nuget package we may not have defaults which can cause errors
|
|
config::initialize_nothrow(nullptr, 0);
|
|
|
|
config::QuickbusNodeCfg cfg;
|
|
if (length > 0 && qbconfig != nullptr && cfg.ParseFromArray(qbconfig, length)) {
|
|
return new Quickbus(cfg);
|
|
}
|
|
|
|
// Note: Eventually api will probably want it's own instance of QB. For now we share
|
|
auto temp_conf = get_client_node_cfg();
|
|
LOG(Api,Info,"Created Quickbus");
|
|
return new Quickbus(temp_conf);
|
|
}
|
|
|
|
CAPI void api_destroy_quickbus(Quickbus* qbus) {
|
|
#ifndef WIN32
|
|
if (qbus != nullptr) {
|
|
delete qbus;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
CAPI void api_post(Quickbus& qbus, const std::function<void()>& callback, uint64_t delayms) {
|
|
qbus.post(callback, delayms);
|
|
}
|
|
|
|
std::string base_topic(const char* type_name, const char* service_qualifier) {
|
|
const std::string topic_base = "api-";
|
|
if (service_qualifier == nullptr ||
|
|
strlen(service_qualifier) == 0) {
|
|
return topic_base + type_name;
|
|
}
|
|
return topic_base + type_name + "-" + service_qualifier;
|
|
}
|
|
|
|
qbus::RPCServiceName rpc_topic(const std::string& base_topic) {
|
|
return qbus::RPCServiceName(base_topic);
|
|
}
|
|
|
|
|
|
//
|
|
// Client
|
|
|
|
struct ApiClient {
|
|
ApiClient(Quickbus& qbus, const std::string& topic) :
|
|
qbus(qbus),
|
|
service_name(topic),
|
|
rpc(qbus, rpc_topic(topic)) {}
|
|
|
|
Quickbus& qbus;
|
|
std::string service_name;
|
|
qbus::RPCClient rpc;
|
|
};
|
|
|
|
CAPI ApiClient* api_client_create(Quickbus* pqbus, const char* type_name, const char* service_qualifier) {
|
|
bts_assert(pqbus);
|
|
auto service_name = base_topic(type_name, service_qualifier);
|
|
LOG(Api,Info, "Create client: {}",service_name);
|
|
ApiClient* pclient = new ApiClient(*pqbus, service_name);
|
|
return pclient;
|
|
}
|
|
|
|
CAPI void api_client_destroy(ApiClient* pclient) {
|
|
if (pclient) {
|
|
LOG(Api,Info, "destroy client: {}",pclient->service_name);
|
|
delete pclient;
|
|
}
|
|
}
|
|
|
|
template <class C>
|
|
void safe_call_fn(const C& cb) {
|
|
#ifdef _MSC_VER
|
|
__try {
|
|
#endif
|
|
cb();
|
|
#ifdef _MSC_VER
|
|
}
|
|
__except(EXCEPTION_EXECUTE_HANDLER) {
|
|
LOG(Api, Error, "Error calling CLR");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void reply_error(ApiError error, raw_response_callback cb, void* extra) {
|
|
ResponseHeader response;
|
|
response.set_code(error);
|
|
auto str = response.SerializeAsString();
|
|
str = (char)(str.size()+1) + str;
|
|
safe_call_fn([&]() {
|
|
cb(str.c_str(), str.size(), extra);
|
|
});
|
|
}
|
|
|
|
|
|
int32_t rpc_connect_timeout_ms() {
|
|
static int32_t t = config::get<config::RPCConfig>().rpc_connect_timeout_seconds() * 1000;
|
|
return t;
|
|
}
|
|
void api_client_send_internal(ApiClient* pclient, qbus::RPCRequest&& request, raw_response_callback cb, int32_t delay, void* extra) {
|
|
bts_assert(pclient);
|
|
bool connected = pclient->rpc.wait_until_connected(0);
|
|
if (!connected && delay > 0) {
|
|
// This branch is the uncommon 'slow path' where connection is taking longer than we expect
|
|
LOG(Api,Diag,"rpc call delay waiting for connection {}",((int64_t)request.value().c_str()));
|
|
pclient->qbus.post([=, request{std::move(request)}]() mutable {
|
|
api_client_send_internal(pclient, std::move(request), cb, delay - 100, extra);
|
|
}, 100);
|
|
return;
|
|
}
|
|
pclient->rpc.send(std::move(request), [=](qbus::RPCError e, const qbus::RPCReplyPtr& reply) {
|
|
LOG(Api,Diag,"rpc reply {}",((int64_t)request.value().c_str()));
|
|
switch (e) {
|
|
case qbus::RPCError::RPC_OK:
|
|
safe_call_fn([&]() {
|
|
cb(reply->value().c_str(), reply->value().size(), extra);
|
|
});
|
|
break;
|
|
case qbus::RPCError::NO_SERVER:
|
|
reply_error(ApiError::NO_SERVER, cb, extra);
|
|
break;
|
|
case qbus::RPCError::SERVER_DISCONNECT:
|
|
reply_error(ApiError::SERVER_DISCONNECT, cb, extra);
|
|
break;
|
|
default:
|
|
reply_error(ApiError::UNEXPECTED_RESPONSE, cb, extra);
|
|
}
|
|
});
|
|
}
|
|
CAPI void api_client_send(ApiClient* pclient, const char* buffer, int buffer_length, raw_response_callback cb, void* extra) {
|
|
LOG(Api,Diag,"send rpc {}",((int64_t)buffer));
|
|
std::string strbuf(buffer, buffer_length);
|
|
qbus::RPCRequest request(move(strbuf));
|
|
api_client_send_internal(pclient, std::move(request), cb, rpc_connect_timeout_ms(), extra);
|
|
}
|
|
|
|
CAPI bool api_client_is_on_qb_thread(ApiClient* pclient) {
|
|
return pclient->qbus.on_qb_thread();
|
|
}
|
|
|
|
|
|
void safe_call_subscription_callback(raw_subscription_callback cb, EventType type, const std::vector<StreamUpdate>& updates, void* extra) {
|
|
safe_call_fn([&]() {
|
|
if (updates.size() > 0)
|
|
cb(type, &updates[0], updates.size(), extra);
|
|
else
|
|
cb(type, nullptr, 0, extra);
|
|
});
|
|
}
|
|
|
|
|
|
bts::qbus::Topic stream_topic(const std::string& base_topic, const char* method, const char* qualifier) {
|
|
if (qualifier == nullptr || strlen(qualifier) == 0) {
|
|
return qbus::Topic(base_topic + "-" + method);
|
|
}
|
|
else {
|
|
return qbus::Topic(base_topic + "-" + method + "/" + qualifier);
|
|
}
|
|
}
|
|
|
|
CAPI TableSubscription api_client_subscribe(ApiClient* pclient, const char* method, const char* qualifier, raw_subscription_callback cb, void* extra) {
|
|
bts_assert(pclient);
|
|
static_assert((int)qbus::TableEvent::EventType::TABLE_CREATED == EventType::TABLE_CREATED, "Keep these Enums in sync");
|
|
static_assert((int)qbus::TableEvent::EventType::TABLE_DESTROYED == EventType::TABLE_DESTROYED, "");
|
|
static_assert((int)qbus::TableEvent::EventType::TABLE_UPDATED == EventType::TABLE_UPDATED, "");
|
|
static_assert((int)qbus::Change::Type::REMOVE == StreamUpdate::REMOVE, "");
|
|
static_assert((int)qbus::Change::Type::REPLACE == StreamUpdate::REPLACE, "");
|
|
static_assert((int)qbus::Change::Type::CREATE == StreamUpdate::CREATE, "");
|
|
|
|
auto topic = stream_topic(pclient->service_name, method, qualifier);
|
|
LOG(Api,Diag,"api subscribe: {}" ,topic.value());
|
|
|
|
auto result = new qbus::TableSubscriber(pclient->qbus, equal_topic(topic), [cb, extra](const qbus::TableEvent& ev) {
|
|
if (ev.event_type == qbus::TableEvent::EventType::TABLE_CREATED ||
|
|
ev.event_type == qbus::TableEvent::EventType::TABLE_DESTROYED) {
|
|
safe_call_fn([&]() {
|
|
cb((EventType)ev.event_type, nullptr, 0, extra);
|
|
});
|
|
}
|
|
else {
|
|
bts_assert(ev.update != nullptr);
|
|
size_t update_count = ev.update->size();
|
|
std::vector<StreamUpdate> bupdates(update_count);
|
|
// Copy structure for interop
|
|
for (size_t x = 0; x < update_count; ++x) {
|
|
const auto& update = (*ev.update)[x];
|
|
bupdates[x].type = static_cast<StreamUpdate::Type>(update.type);
|
|
bupdates[x].key = update.key.value();
|
|
const auto& val = update.value->value();
|
|
bupdates[x].buffer_len = val.size();
|
|
bupdates[x].buffer = val.c_str();
|
|
}
|
|
safe_call_subscription_callback(cb, (EventType)ev.event_type, bupdates, extra);
|
|
}
|
|
}, true);
|
|
|
|
return result;
|
|
}
|
|
|
|
CAPI void api_client_unsubscribe(ApiClient* pclient, TableSubscription pvtable) {
|
|
qbus::TableSubscriber* ptable = static_cast<qbus::TableSubscriber*>(pvtable);
|
|
if (pclient && ptable) {
|
|
ptable->disconnect();
|
|
delete ptable;
|
|
}
|
|
}
|
|
|
|
CAPI void api_client_log_error(const char* message) {
|
|
// managed api dll does not include logging but native api does `\_(o,o)_/`
|
|
std::string msg(message);
|
|
utils::Dispatcher::background().invoke([msg]() {
|
|
LOG(Api, Error, "{}", msg);
|
|
});
|
|
}
|
|
|
|
}}// namespace
|
|
|
|
|