mirror of
https://github.com/RetroShare/RetroShare.git
synced 2025-07-12 09:19:32 -04:00
661 lines
19 KiB
C++
661 lines
19 KiB
C++
/*
|
|
* RetroShare JSON API
|
|
*
|
|
* Copyright (C) 2018-2019 Gioacchino Mazzurco <gio@eigenlab.org>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU Affero General Public License as published by the
|
|
* Free Software Foundation, version 3.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE.
|
|
* See the GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
*
|
|
* SPDX-FileCopyrightText: 2004-2019 RetroShare Team <contact@retroshare.cc>
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <memory>
|
|
#include <restbed>
|
|
#include <vector>
|
|
#include <openssl/crypto.h>
|
|
|
|
|
|
#include "jsonapi.h"
|
|
|
|
#include "util/rsjson.h"
|
|
#include "retroshare/rsfiles.h"
|
|
#include "util/radix64.h"
|
|
#include "retroshare/rsinit.h"
|
|
#include "util/rsnet.h"
|
|
#include "retroshare/rsiface.h"
|
|
#include "retroshare/rsinit.h"
|
|
#include "util/rsurl.h"
|
|
#include "util/rstime.h"
|
|
#include "retroshare/rsevents.h"
|
|
#include "retroshare/rsversion.h"
|
|
|
|
// Generated at compile time
|
|
#include "jsonapi-includes.inl"
|
|
|
|
/*extern*/ RsJsonApi* rsJsonApi = nullptr;
|
|
|
|
const std::string RsJsonApi::DEFAULT_BINDING_ADDRESS = "127.0.0.1";
|
|
|
|
/*static*/ const std::multimap<std::string, std::string>
|
|
JsonApiServer::corsHeaders =
|
|
{
|
|
{ "Access-Control-Allow-Origin", "*" },
|
|
{ "Access-Control-Allow-Methods", "GET, POST, OPTIONS"},
|
|
{ "Access-Control-Allow-Headers", "Authorization,DNT,User-Agent,"
|
|
"X-Requested-With,If-Modified-Since,"
|
|
"Cache-Control,Content-Type,Range" },
|
|
{ "Access-Control-Expose-Headers", "Content-Length,Content-Range" }
|
|
};
|
|
|
|
/*static*/ const std::multimap<std::string, std::string>
|
|
JsonApiServer::corsOptionsHeaders =
|
|
{
|
|
{ "Access-Control-Allow-Origin", "*" },
|
|
{ "Access-Control-Allow-Methods", "GET, POST, OPTIONS"},
|
|
{ "Access-Control-Allow-Headers", "Authorization,DNT,User-Agent,"
|
|
"X-Requested-With,If-Modified-Since,"
|
|
"Cache-Control,Content-Type,Range" },
|
|
{ "Access-Control-Max-Age", "1728000" }, // 20 days
|
|
{ "Content-Type", "text/plain; charset=utf-8" },
|
|
{ "Content-Length", "0" }
|
|
};
|
|
|
|
#define INITIALIZE_API_CALL_JSON_CONTEXT \
|
|
RsGenericSerializer::SerializeContext cReq( \
|
|
nullptr, 0, \
|
|
RsGenericSerializer::SERIALIZATION_FLAG_YIELDING ); \
|
|
RsJson& jReq(cReq.mJson); \
|
|
if(session->get_request()->get_method() == "GET") \
|
|
{ \
|
|
const std::string jrqp(session->get_request()->get_query_parameter("jsonData")); \
|
|
jReq.Parse(jrqp.c_str(), jrqp.size()); \
|
|
} \
|
|
else \
|
|
jReq.Parse(reinterpret_cast<const char*>(body.data()), body.size()); \
|
|
\
|
|
RsGenericSerializer::SerializeContext cAns; \
|
|
RsJson& jAns(cAns.mJson); \
|
|
\
|
|
/* if caller specified caller_data put it back in the answhere */ \
|
|
const char kcd[] = "caller_data"; \
|
|
if(jReq.HasMember(kcd)) \
|
|
jAns.AddMember(kcd, jReq[kcd], jAns.GetAllocator())
|
|
|
|
#define DEFAULT_API_CALL_JSON_RETURN(RET_CODE) \
|
|
std::stringstream ss; \
|
|
ss << jAns; \
|
|
std::string&& ans(ss.str()); \
|
|
auto headers = corsHeaders; \
|
|
headers.insert({ "Content-Type", "text/json" }); \
|
|
headers.insert({ "Content-Length", std::to_string(ans.length()) }); \
|
|
session->close(RET_CODE, ans, headers)
|
|
|
|
|
|
/*static*/ bool JsonApiServer::checkRsServicePtrReady(
|
|
const void* serviceInstance, const std::string& serviceName,
|
|
RsGenericSerializer::SerializeContext& ctx,
|
|
const std::shared_ptr<rb::Session> session )
|
|
{
|
|
if(serviceInstance) return true;
|
|
|
|
std::string jsonApiError = __PRETTY_FUNCTION__;
|
|
jsonApiError += "Service: ";
|
|
jsonApiError += serviceName;
|
|
jsonApiError += " not initialized!";
|
|
|
|
RsGenericSerializer::SerializeJob j(RsGenericSerializer::TO_JSON);
|
|
RS_SERIAL_PROCESS(jsonApiError);
|
|
|
|
RsJson& jAns(ctx.mJson);
|
|
DEFAULT_API_CALL_JSON_RETURN(rb::CONFLICT);
|
|
return false;
|
|
}
|
|
|
|
bool RsJsonApi::parseToken(
|
|
const std::string& clear_token, std::string& user,std::string& passwd )
|
|
{
|
|
uint32_t colonIndex = 0;
|
|
uint32_t colonCounter = 0;
|
|
const auto tkLen = clear_token.length();
|
|
|
|
for(uint32_t i=0; i < tkLen && colonCounter < 2; ++i)
|
|
if(clear_token[i] == ':') { ++colonCounter; colonIndex = i; }
|
|
else if(!librs::util::is_alphanumeric(clear_token[i])) return false;
|
|
|
|
if(colonCounter != 1) return false;
|
|
|
|
user = clear_token.substr(0, colonIndex);
|
|
passwd = clear_token.substr(colonIndex + 1);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"),
|
|
mService(std::make_shared<restbed::Service>()),
|
|
mListeningPort(RsJsonApi::DEFAULT_PORT),
|
|
mBindingAddress(RsJsonApi::DEFAULT_BINDING_ADDRESS)
|
|
{
|
|
registerHandler("/rsLoginHelper/createLocation",
|
|
[this](const std::shared_ptr<rb::Session> session)
|
|
{
|
|
auto reqSize = session->get_request()->get_header("Content-Length", 0);
|
|
session->fetch( static_cast<size_t>(reqSize), [this](
|
|
const std::shared_ptr<rb::Session> session,
|
|
const rb::Bytes& body )
|
|
{
|
|
INITIALIZE_API_CALL_JSON_CONTEXT;
|
|
|
|
RsLoginHelper::Location location;
|
|
std::string password;
|
|
std::string errorMessage;
|
|
bool makeHidden = false;
|
|
bool makeAutoTor = false;
|
|
|
|
// deserialize input parameters from JSON
|
|
{
|
|
RsGenericSerializer::SerializeContext& ctx(cReq);
|
|
RsGenericSerializer::SerializeJob j(RsGenericSerializer::FROM_JSON);
|
|
RS_SERIAL_PROCESS(location);
|
|
RS_SERIAL_PROCESS(password);
|
|
RS_SERIAL_PROCESS(makeHidden);
|
|
RS_SERIAL_PROCESS(makeAutoTor);
|
|
}
|
|
|
|
// call retroshare C++ API
|
|
bool retval = rsLoginHelper->createLocation(
|
|
location, password, errorMessage, makeHidden,
|
|
makeAutoTor );
|
|
|
|
if(retval)
|
|
authorizeUser(location.mLocationId.toStdString(),password);
|
|
|
|
// serialize out parameters and return value to JSON
|
|
{
|
|
RsGenericSerializer::SerializeContext& ctx(cAns);
|
|
RsGenericSerializer::SerializeJob j(RsGenericSerializer::TO_JSON);
|
|
RS_SERIAL_PROCESS(location);
|
|
RS_SERIAL_PROCESS(errorMessage);
|
|
RS_SERIAL_PROCESS(retval);
|
|
}
|
|
|
|
// return them to the API caller
|
|
DEFAULT_API_CALL_JSON_RETURN(rb::OK);
|
|
} );
|
|
}, false);
|
|
|
|
registerHandler("/rsLoginHelper/attemptLogin",
|
|
[this](const std::shared_ptr<rb::Session> session)
|
|
{
|
|
auto reqSize = session->get_request()->get_header("Content-Length", 0);
|
|
session->fetch( static_cast<size_t>(reqSize), [this](
|
|
const std::shared_ptr<rb::Session> session,
|
|
const rb::Bytes& body )
|
|
{
|
|
INITIALIZE_API_CALL_JSON_CONTEXT;
|
|
|
|
RsPeerId account;
|
|
std::string password;
|
|
|
|
// deserialize input parameters from JSON
|
|
{
|
|
RsGenericSerializer::SerializeContext& ctx(cReq);
|
|
RsGenericSerializer::SerializeJob j(RsGenericSerializer::FROM_JSON);
|
|
RS_SERIAL_PROCESS(account);
|
|
RS_SERIAL_PROCESS(password);
|
|
}
|
|
|
|
// call retroshare C++ API
|
|
RsInit::LoadCertificateStatus retval =
|
|
rsLoginHelper->attemptLogin(account, password);
|
|
|
|
if( retval == RsInit::OK )
|
|
authorizeUser(account.toStdString(),password);
|
|
|
|
// serialize out parameters and return value to JSON
|
|
{
|
|
RsGenericSerializer::SerializeContext& ctx(cAns);
|
|
RsGenericSerializer::SerializeJob j(RsGenericSerializer::TO_JSON);
|
|
RS_SERIAL_PROCESS(retval);
|
|
}
|
|
|
|
// return them to the API caller
|
|
DEFAULT_API_CALL_JSON_RETURN(rb::OK);
|
|
} );
|
|
}, false);
|
|
|
|
registerHandler("/rsControl/rsGlobalShutDown",
|
|
[](const std::shared_ptr<rb::Session> session)
|
|
{
|
|
auto reqSize = session->get_request()->get_header("Content-Length", 0);
|
|
session->fetch( static_cast<size_t>(reqSize), [](
|
|
const std::shared_ptr<rb::Session> session,
|
|
const rb::Bytes& body )
|
|
{
|
|
INITIALIZE_API_CALL_JSON_CONTEXT;
|
|
DEFAULT_API_CALL_JSON_RETURN(rb::OK);
|
|
rsControl->rsGlobalShutDown();
|
|
} );
|
|
}, true);
|
|
|
|
registerHandler("/rsFiles/getFileData",
|
|
[](const std::shared_ptr<rb::Session> session)
|
|
{
|
|
auto reqSize = session->get_request()->get_header("Content-Length", 0);
|
|
session->fetch( static_cast<size_t>(reqSize), [](
|
|
const std::shared_ptr<rb::Session> session,
|
|
const rb::Bytes& body )
|
|
{
|
|
INITIALIZE_API_CALL_JSON_CONTEXT;
|
|
|
|
if(!checkRsServicePtrReady(rsFiles, "rsFiles", cAns, session))
|
|
return;
|
|
|
|
RsFileHash hash;
|
|
uint64_t offset;
|
|
uint32_t requested_size;
|
|
bool retval = false;
|
|
std::string errorMessage;
|
|
std::string base64data;
|
|
|
|
// deserialize input parameters from JSON
|
|
{
|
|
RsGenericSerializer::SerializeContext& ctx(cReq);
|
|
RsGenericSerializer::SerializeJob j(RsGenericSerializer::FROM_JSON);
|
|
RS_SERIAL_PROCESS(hash);
|
|
RS_SERIAL_PROCESS(offset);
|
|
RS_SERIAL_PROCESS(requested_size);
|
|
}
|
|
|
|
if(requested_size > 10485760)
|
|
errorMessage = "requested_size is too big! Better less then 1M";
|
|
else
|
|
{
|
|
std::vector<uint8_t> buffer(requested_size);
|
|
|
|
// call retroshare C++ API
|
|
retval = rsFiles->getFileData(
|
|
hash, offset, requested_size, buffer.data());
|
|
|
|
Radix64::encode(buffer.data(), requested_size, base64data);
|
|
}
|
|
|
|
// serialize out parameters and return value to JSON
|
|
{
|
|
RsGenericSerializer::SerializeContext& ctx(cAns);
|
|
RsGenericSerializer::SerializeJob j(RsGenericSerializer::TO_JSON);
|
|
RS_SERIAL_PROCESS(retval);
|
|
RS_SERIAL_PROCESS(requested_size);
|
|
RS_SERIAL_PROCESS(base64data);
|
|
if(!errorMessage.empty()) RS_SERIAL_PROCESS(errorMessage);
|
|
}
|
|
|
|
DEFAULT_API_CALL_JSON_RETURN(rb::OK);
|
|
} );
|
|
}, true);
|
|
|
|
registerHandler("/rsEvents/registerEventsHandler",
|
|
[this](const std::shared_ptr<rb::Session> session)
|
|
{
|
|
const std::multimap<std::string, std::string> headers
|
|
{
|
|
{ "Connection", "keep-alive" },
|
|
{ "Content-Type", "text/event-stream" }
|
|
};
|
|
session->yield(rb::OK, headers);
|
|
|
|
size_t reqSize = static_cast<size_t>(
|
|
session->get_request()->get_header("Content-Length", 0) );
|
|
session->fetch( reqSize, [this](
|
|
const std::shared_ptr<rb::Session> session,
|
|
const rb::Bytes& body )
|
|
{
|
|
INITIALIZE_API_CALL_JSON_CONTEXT;
|
|
|
|
if( !checkRsServicePtrReady(
|
|
rsEvents, "rsEvents", cAns, session ) )
|
|
return;
|
|
|
|
const std::weak_ptr<rb::Session> weakSession(session);
|
|
RsEventsHandlerId_t hId = rsEvents->generateUniqueHandlerId();
|
|
std::function<void(std::shared_ptr<const RsEvent>)> multiCallback =
|
|
[this, weakSession, hId](std::shared_ptr<const RsEvent> event)
|
|
{
|
|
mService->schedule( [weakSession, hId, event]()
|
|
{
|
|
auto session = weakSession.lock();
|
|
if(!session || session->is_closed())
|
|
{
|
|
if(rsEvents) rsEvents->unregisterEventsHandler(hId);
|
|
return;
|
|
}
|
|
|
|
RsGenericSerializer::SerializeContext ctx;
|
|
RsTypeSerializer::serial_process(
|
|
RsGenericSerializer::TO_JSON, ctx,
|
|
*const_cast<RsEvent*>(event.get()), "event" );
|
|
|
|
std::stringstream message;
|
|
message << "data: " << compactJSON << ctx.mJson << "\n\n";
|
|
|
|
session->yield(message.str());
|
|
} );
|
|
};
|
|
|
|
bool retval = rsEvents->registerEventsHandler(multiCallback, hId);
|
|
|
|
{
|
|
RsGenericSerializer::SerializeContext& ctx(cAns);
|
|
RsGenericSerializer::SerializeJob j(RsGenericSerializer::TO_JSON);
|
|
RS_SERIAL_PROCESS(retval);
|
|
}
|
|
|
|
// return them to the API caller
|
|
std::stringstream message;
|
|
message << "data: " << compactJSON << cAns.mJson << "\n\n";
|
|
session->yield(message.str());
|
|
} );
|
|
}, true);
|
|
// Generated at compile time
|
|
#include "jsonapi-wrappers.inl"
|
|
}
|
|
|
|
void JsonApiServer::registerHandler(
|
|
const std::string& path,
|
|
const std::function<void (const std::shared_ptr<restbed::Session>)>& handler,
|
|
bool requiresAutentication )
|
|
{
|
|
std::shared_ptr<restbed::Resource> resource(new rb::Resource);
|
|
resource->set_path(path);
|
|
resource->set_method_handler("GET", handler);
|
|
resource->set_method_handler("POST", handler);
|
|
resource->set_method_handler("OPTIONS", handleCorsOptions);
|
|
|
|
if(requiresAutentication)
|
|
resource->set_authentication_handler(
|
|
[this](
|
|
const std::shared_ptr<rb::Session> session,
|
|
const std::function<void (const std::shared_ptr<rb::Session>)>& callback )
|
|
{
|
|
if(session->get_request()->get_method() == "OPTIONS")
|
|
{
|
|
callback(session);
|
|
return;
|
|
}
|
|
|
|
if(!rsLoginHelper->isLoggedIn())
|
|
{
|
|
session->close(rb::CONFLICT);
|
|
return;
|
|
}
|
|
|
|
std::istringstream authHeader;
|
|
authHeader.str(session->get_request()->get_header("Authorization"));
|
|
|
|
std::string authToken;
|
|
std::getline(authHeader, authToken, ' ');
|
|
|
|
if(authToken != "Basic")
|
|
{
|
|
session->close(rb::UNAUTHORIZED);
|
|
return;
|
|
}
|
|
|
|
std::getline(authHeader, authToken, ' ');
|
|
authToken = decodeToken(authToken);
|
|
|
|
if(isAuthTokenValid(authToken)) callback(session);
|
|
else session->close(rb::UNAUTHORIZED);
|
|
} );
|
|
|
|
mResources.push_back(resource);
|
|
}
|
|
|
|
void JsonApiServer::setNewAccessRequestCallback(
|
|
const std::function<bool (const std::string&, const std::string&)>& callback )
|
|
{ mNewAccessRequestCallback = callback; }
|
|
|
|
bool JsonApiServer::requestNewTokenAutorization(
|
|
const std::string& user, const std::string& passwd )
|
|
{
|
|
if(rsLoginHelper->isLoggedIn() && mNewAccessRequestCallback(user, passwd))
|
|
return authorizeUser(user, passwd);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool JsonApiServer::isAuthTokenValid(const std::string& token)
|
|
{
|
|
RS_STACK_MUTEX(configMutex);
|
|
|
|
std::string user,passwd;
|
|
if(!parseToken(token,user,passwd)) return false;
|
|
|
|
auto it = mAuthTokenStorage.mAuthorizedTokens.find(user);
|
|
if(it == mAuthTokenStorage.mAuthorizedTokens.end()) return false;
|
|
|
|
// attempt avoiding +else CRYPTO_memcmp+ being optimized away
|
|
int noOptimiz = 1;
|
|
|
|
/* Do not use mAuthTokenStorage.mAuthorizedTokens.count(token), because
|
|
* std::string comparison is usually not constant time on content to be
|
|
* faster, so an attacker may use timings to guess authorized tokens */
|
|
|
|
if( passwd.size() == it->second.size() &&
|
|
( noOptimiz = CRYPTO_memcmp(
|
|
passwd.data(), it->second.data(), it->second.size() ) ) == 0 )
|
|
return true;
|
|
// Make token size guessing harder
|
|
else noOptimiz = CRYPTO_memcmp(passwd.data(), passwd.data(), passwd.size());
|
|
|
|
// attempt avoiding +else CRYPTO_memcmp+ being optimized away
|
|
return static_cast<uint32_t>(noOptimiz) + 1 == 0;
|
|
}
|
|
|
|
std::map<std::string, std::string> JsonApiServer::getAuthorizedTokens()
|
|
{
|
|
RS_STACK_MUTEX(configMutex);
|
|
return mAuthTokenStorage.mAuthorizedTokens;
|
|
}
|
|
|
|
bool JsonApiServer::revokeAuthToken(const std::string& token)
|
|
{
|
|
RS_STACK_MUTEX(configMutex);
|
|
if(mAuthTokenStorage.mAuthorizedTokens.erase(token))
|
|
{
|
|
IndicateConfigChanged();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void JsonApiServer::connectToConfigManager(p3ConfigMgr& cfgmgr)
|
|
{
|
|
cfgmgr.addConfiguration("jsonapi.cfg",this);
|
|
|
|
RsFileHash hash;
|
|
loadConfiguration(hash);
|
|
}
|
|
|
|
bool JsonApiServer::authorizeUser(
|
|
const std::string& user, const std::string& passwd )
|
|
{
|
|
if(!librs::util::is_alphanumeric(user))
|
|
{
|
|
RsErr() << __PRETTY_FUNCTION__ << " User name is not alphanumeric"
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
if(!librs::util::is_alphanumeric(passwd))
|
|
{
|
|
RsErr() << __PRETTY_FUNCTION__ << " Password is not alphanumeric"
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
RS_STACK_MUTEX(configMutex);
|
|
|
|
std::string& p(mAuthTokenStorage.mAuthorizedTokens[user]);
|
|
if(p != passwd)
|
|
{
|
|
p = passwd;
|
|
IndicateConfigChanged();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*static*/ std::string JsonApiServer::decodeToken(const std::string& radix64_token)
|
|
{
|
|
std::vector<uint8_t> decodedVect(Radix64::decode(radix64_token));
|
|
std::string decodedToken(
|
|
reinterpret_cast<const char*>(&decodedVect[0]),
|
|
decodedVect.size() );
|
|
return decodedToken;
|
|
}
|
|
|
|
RsSerialiser* JsonApiServer::setupSerialiser()
|
|
{
|
|
RsSerialiser* rss = new RsSerialiser;
|
|
rss->addSerialType(new JsonApiConfigSerializer);
|
|
return rss;
|
|
}
|
|
|
|
bool JsonApiServer::saveList(bool& cleanup, std::list<RsItem*>& saveItems)
|
|
{
|
|
cleanup = false;
|
|
configMutex.lock();
|
|
saveItems.push_back(&mAuthTokenStorage);
|
|
return true;
|
|
}
|
|
|
|
bool JsonApiServer::loadList(std::list<RsItem*>& loadList)
|
|
{
|
|
for(RsItem* it : loadList)
|
|
switch (static_cast<JsonApiItemsType>(it->PacketSubType()))
|
|
{
|
|
case JsonApiItemsType::AuthTokenItem:
|
|
mAuthTokenStorage = *static_cast<JsonApiServerAuthTokenStorage*>(it);
|
|
delete it;
|
|
break;
|
|
default:
|
|
delete it;
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void JsonApiServer::saveDone() { configMutex.unlock(); }
|
|
|
|
void JsonApiServer::handleCorsOptions(
|
|
const std::shared_ptr<restbed::Session> session )
|
|
{ session->close(rb::NO_CONTENT, corsOptionsHeaders); }
|
|
|
|
void JsonApiServer::registerResourceProvider(const JsonApiResourceProvider& rp)
|
|
{ mResourceProviders.insert(rp); }
|
|
void JsonApiServer::unregisterResourceProvider(const JsonApiResourceProvider& rp)
|
|
{ mResourceProviders.erase(rp); }
|
|
bool JsonApiServer::hasResourceProvider(const JsonApiResourceProvider& rp)
|
|
{ return mResourceProviders.find(rp) != mResourceProviders.end(); }
|
|
|
|
std::vector<std::shared_ptr<rb::Resource> > JsonApiServer::getResources() const
|
|
{
|
|
auto tab = mResources;
|
|
|
|
for(auto& rp: mResourceProviders)
|
|
for(auto r: rp.get().getResources()) tab.push_back(r);
|
|
|
|
return tab;
|
|
}
|
|
|
|
bool JsonApiServer::restart()
|
|
{
|
|
fullstop();
|
|
RsThread::start("JSON API Server");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool JsonApiServer::fullstop()
|
|
{
|
|
if(!mService->is_up()) return true;
|
|
|
|
mService->stop();
|
|
RsThread::ask_for_stop();
|
|
|
|
while(isRunning())
|
|
{
|
|
RsDbg() << __PRETTY_FUNCTION__ << " shutting down JSON API service."
|
|
<< std::endl;
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
uint16_t JsonApiServer::listeningPort() const { return mListeningPort; }
|
|
void JsonApiServer::setListeningPort(uint16_t p) { mListeningPort = p; }
|
|
void JsonApiServer::setBindingAddress(const std::string& bindAddress)
|
|
{ mBindingAddress = bindAddress; }
|
|
std::string JsonApiServer::getBindingAddress() const { return mBindingAddress; }
|
|
|
|
void JsonApiServer::runloop()
|
|
{
|
|
auto settings = std::make_shared<restbed::Settings>();
|
|
settings->set_port(mListeningPort);
|
|
settings->set_bind_address(mBindingAddress);
|
|
settings->set_default_header("Connection", "close");
|
|
|
|
if(mService->is_up())
|
|
{
|
|
RsWarn() << __PRETTY_FUNCTION__ << " restbed is already running. "
|
|
<< " stopping it before starting again!" << std::endl;
|
|
mService->stop();
|
|
}
|
|
|
|
/* re-allocating mService is important because it deletes the existing
|
|
* service and therefore leaves the listening port open */
|
|
mService = std::make_shared<restbed::Service>();
|
|
|
|
for(auto& r: getResources()) mService->publish(r);
|
|
|
|
try
|
|
{
|
|
RsUrl apiUrl; apiUrl.setScheme("http").setHost(mBindingAddress)
|
|
.setPort(mListeningPort);
|
|
RsDbg() << __PRETTY_FUNCTION__ << " JSON API server listening on "
|
|
<< apiUrl.toString() << std::endl;
|
|
mService->start(settings);
|
|
}
|
|
catch(std::exception& e)
|
|
{
|
|
RsErr() << __PRETTY_FUNCTION__ << " Failure starting JSON API server: "
|
|
<< e.what() << std::endl;
|
|
print_stacktrace();
|
|
return;
|
|
}
|
|
|
|
RsInfo() << __PRETTY_FUNCTION__ << " finished!" << std::endl;
|
|
}
|
|
|
|
/*static*/ void RsJsonApi::version(
|
|
uint32_t& major, uint32_t& minor, uint32_t& mini, std::string& extra,
|
|
std::string& human )
|
|
{
|
|
major = RS_MAJOR_VERSION;
|
|
minor = RS_MINOR_VERSION;
|
|
mini = RS_MINI_VERSION;
|
|
extra = RS_EXTRA_VERSION;
|
|
human = RS_HUMAN_READABLE_VERSION;
|
|
}
|