#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 #endif namespace bts { namespace api { config::QuickbusNodeCfg get_client_node_cfg() { auto cfg = config::get(); 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_ip() ); node_cfg.set_parent_port( cfg.has_qbus_port() ? cfg.qbus_port() : config::get().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& 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 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().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& 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 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(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(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