David Gerber a73ea961c6 Fix JSON API mime type
Most clients expect application/json which is standard, instead
of text/json.
2021-01-24 01:53:58 +01:00

836 lines
25 KiB
C++

/*
* RetroShare JSON API
*
* Copyright (C) 2018-2020 Gioacchino Mazzurco <gio@eigenlab.org>
* Copyright (C) 2019-2020 Asociación Civil Altermundi <info@altermundi.net>
*
* 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-2020 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" }
};
/* static */ const RsJsonApiErrorCategory RsJsonApiErrorCategory::instance;
#define INITIALIZE_API_CALL_JSON_CONTEXT \
RsGenericSerializer::SerializeContext cReq( \
nullptr, 0, \
RsSerializationFlags::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", "application/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;
}
void JsonApiServer::unProtectedRestart()
{
/* Extremely sensitive stuff!
* Make sure you read documentation in header before changing or use!! */
fullstop();
RsThread::start("JSON API Server");
}
bool RsJsonApi::parseToken(
const std::string& clear_token, std::string& user,std::string& passwd )
{
auto colonIndex = std::string::npos;
const auto tkLen = clear_token.length();
for(uint32_t i=0; i < tkLen; ++i)
if(clear_token[i] == ':') { colonIndex = i; break; }
user = clear_token.substr(0, colonIndex);
if(colonIndex < tkLen)
passwd = clear_token.substr(colonIndex + 1);
return true;
}
JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"),
mService(nullptr),
mListeningPort(RsJsonApi::DEFAULT_PORT),
mBindingAddress(RsJsonApi::DEFAULT_BINDING_ADDRESS),
mRestartReqTS(0)
{
#if defined(RS_THREAD_FORCE_STOP) && defined(RS_JSONAPI_DEBUG_SERVICE_STOP)
/* When called in bursts it seems that Restbed::Service::stop() doesn't
* always does the job, to debug those cases it has been useful to ask
* RsThread to force it to stop for us. */
RsThread::setStopTimeout(10);
#endif
#if !RS_VERSION_AT_LEAST(0,6,6)
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;
std::string createToken;
// 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);
RS_SERIAL_PROCESS(createToken);
}
// call retroshare C++ API
bool retval = rsLoginHelper->createLocation(
location, password, errorMessage, makeHidden,
makeAutoTor );
std::string tokenUser, tokenPw;
if(retval && parseToken(createToken, tokenUser, tokenPw))
authorizeUser(tokenUser,tokenPw);
// 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);
#endif // !RS_VERSION_AT_LEAST(0,6,6)
registerHandler("/rsLoginHelper/createLocationV2",
[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 locationId;
RsPgpId pgpId;
std::string locationName;
std::string pgpName;
std::string password;
// JSON API only
std::string apiUser;
std::string apiPass;
// deserialize input parameters from JSON
{
RsGenericSerializer::SerializeContext& ctx(cReq);
RsGenericSerializer::SerializeJob j(RsGenericSerializer::FROM_JSON);
RS_SERIAL_PROCESS(locationId);
RS_SERIAL_PROCESS(pgpId);
RS_SERIAL_PROCESS(locationName);
RS_SERIAL_PROCESS(pgpName);
RS_SERIAL_PROCESS(password);
// JSON API only
RS_SERIAL_PROCESS(apiUser);
RS_SERIAL_PROCESS(apiPass);
}
std::error_condition retval;
if(apiUser.empty())
retval = RsJsonApiErrorNum::TOKEN_FORMAT_INVALID;
if(!retval)
retval = badApiCredientalsFormat(apiUser, apiPass);
if(!retval) // call retroshare C++ API
retval = rsLoginHelper->createLocationV2(
locationId, pgpId, locationName, pgpName, password );
if(!retval) retval = authorizeUser(apiUser, apiPass);
// serialize out parameters and return value to JSON
{
RsGenericSerializer::SerializeContext& ctx(cAns);
RsGenericSerializer::SerializeJob j(RsGenericSerializer::TO_JSON);
RS_SERIAL_PROCESS(locationId);
RS_SERIAL_PROCESS(pgpId);
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);
/* Wrap inside RsThread::async because this call
* RsThread::fullstop() also on JSON API server thread.
* Calling RsThread::fullstop() from it's own thread should never
* happen and if it happens an error message is printed
* accordingly by RsThread::fullstop() */
RsThread::async([](){ 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::weak_ptr<rb::Service> weakService(mService);
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, [weakService](
const std::shared_ptr<rb::Session> session,
const rb::Bytes& body )
{
INITIALIZE_API_CALL_JSON_CONTEXT;
if( !checkRsServicePtrReady(
rsEvents, "rsEvents", cAns, session ) )
return;
RsEventType eventType = RsEventType::__NONE;
// deserialize input parameters from JSON
{
RsGenericSerializer::SerializeContext& ctx(cReq);
RsGenericSerializer::SerializeJob j(RsGenericSerializer::FROM_JSON);
RS_SERIAL_PROCESS(eventType);
}
const std::weak_ptr<rb::Session> weakSession(session);
RsEventsHandlerId_t hId = rsEvents->generateUniqueHandlerId();
std::function<void(std::shared_ptr<const RsEvent>)> multiCallback =
[weakSession, weakService, hId](
std::shared_ptr<const RsEvent> event )
{
auto lService = weakService.lock();
if(!lService || lService->is_down())
{
if(rsEvents) rsEvents->unregisterEventsHandler(hId);
return;
}
lService->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());
} );
};
std::error_condition retval = rsEvents->registerEventsHandler(
multiCallback, hId, eventType );
{
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);
registerHandler("/rsJsonApi/restart",
[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;
std::error_condition retval;
const auto now = time(nullptr);
if(mRestartReqTS.exchange(now) + RESTART_BURST_PROTECTION > now)
retval = RsJsonApiErrorNum::NOT_A_MACHINE_GUN;
// serialize out parameters and return value to JSON
{
RsGenericSerializer::SerializeContext& ctx(cAns);
RsGenericSerializer::SerializeJob j(RsGenericSerializer::TO_JSON);
RS_SERIAL_PROCESS(retval);
}
DEFAULT_API_CALL_JSON_RETURN(rb::OK);
/* Wrap inside RsThread::async because this call fullstop() on
* JSON API server thread.
* Calling RsThread::fullstop() from it's own thread should never
* happen and if it happens an error message is printed
* accordingly by RsThread::fullstop() */
if(!retval) RsThread::async([this](){ unProtectedRestart(); });
} );
}, 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, path](
const std::shared_ptr<rb::Session> session,
const std::function<void (const std::shared_ptr<rb::Session>)>& callback )
{
/* Declare outside the lambda to avoid returning a dangling
* reference */
RsWarn tWarn;
const auto authFail =
[&](int status) -> std::ostream&
{
/* Capture session by reference as it is cheaper then copying
* shared_ptr by value which is not needed in this case */
session->close(status, corsOptionsHeaders);
return tWarn << "JsonApiServer authentication handler "
"blocked an attempt to call JSON API "
"authenticated method: " << path;
};
if(session->get_request()->get_method() == "OPTIONS")
{
callback(session);
return;
}
if(!rsLoginHelper->isLoggedIn())
{
authFail(rb::CONFLICT) << " before RetroShare login"
<< std::endl;
return;
}
std::istringstream authHeader;
authHeader.str(session->get_request()->get_header("Authorization"));
std::string authToken;
std::getline(authHeader, authToken, ' ');
if(authToken != "Basic")
{
authFail(rb::UNAUTHORIZED)
<< " with wrong Authorization header: "
<< authHeader.str() << std::endl;
return;
}
std::getline(authHeader, authToken, ' ');
authToken = decodeToken(authToken);
std::error_condition ec;
if(isAuthTokenValid(authToken, ec)) callback(session);
else
{
std::string tUser;
parseToken(authToken, tUser, RS_DEFAULT_STORAGE_PARAM(std::string));
authFail(rb::UNAUTHORIZED)
<< " user: " << tUser << ec << std::endl;
}
} );
mResources.push_back(resource);
}
void JsonApiServer::setNewAccessRequestCallback(
const std::function<bool (const std::string&, const std::string&)>& callback )
{ mNewAccessRequestCallback = callback; }
/*static*/ std::error_condition JsonApiServer::badApiCredientalsFormat(
const std::string& user, const std::string& passwd )
{
if(user.find(':') < std::string::npos)
return RsJsonApiErrorNum::API_USER_CONTAIN_COLON;
if(user.empty())
RsWarn() << __PRETTY_FUNCTION__ << " User is empty, are you sure "
<< "this what you wanted?" << std::endl;
if(passwd.empty())
RsWarn() << __PRETTY_FUNCTION__ << " Password is empty, are you sure "
<< "this what you wanted?" << std::endl;
return std::error_condition();
}
std::error_condition JsonApiServer::requestNewTokenAutorization(
const std::string& user, const std::string& passwd )
{
auto ec = badApiCredientalsFormat(user, passwd);
if(ec) return ec;
if(!rsLoginHelper->isLoggedIn())
return RsJsonApiErrorNum::CANNOT_EXECUTE_BEFORE_RS_LOGIN;
if(mNewAccessRequestCallback(user, passwd))
return authorizeUser(user, passwd);
return RsJsonApiErrorNum::AUTHORIZATION_REQUEST_DENIED;
}
bool JsonApiServer::isAuthTokenValid(
const std::string& token, std::error_condition& error )
{
RS_STACK_MUTEX(configMutex);
const auto failure = [&error](RsJsonApiErrorNum e) -> bool
{
error = e;
return false;
};
const auto success = [&error]()
{
error.clear();
return true;
};
std::string user,passwd;
if(!parseToken(token, user, passwd))
return failure(RsJsonApiErrorNum::TOKEN_FORMAT_INVALID);
auto it = mAuthTokenStorage.mAuthorizedTokens.find(user);
if(it == mAuthTokenStorage.mAuthorizedTokens.end())
return failure(RsJsonApiErrorNum::UNKNOWN_API_USER);
// 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 success();
// Make token size guessing harder
else noOptimiz = CRYPTO_memcmp(passwd.data(), passwd.data(), passwd.size());
/* At this point we are sure password is wrong, and one could think to
* plainly `return false` still this ugly and apparently unuseful extra
* calculation is here to avoid `else CRYPTO_memcmp` being optimized away,
* so a pontential attacker cannot guess password size based on timing */
return static_cast<uint32_t>(noOptimiz) + 1 == 0 ?
success() : failure(RsJsonApiErrorNum::WRONG_API_PASSWORD);
}
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);
}
std::error_condition JsonApiServer::authorizeUser(
const std::string& user, const std::string& passwd )
{
auto ec = badApiCredientalsFormat(user, passwd);
if(ec) return ec;
RS_STACK_MUTEX(configMutex);
std::string& p(mAuthTokenStorage.mAuthorizedTokens[user]);
if(p != passwd)
{
p = passwd;
IndicateConfigChanged();
}
return ec;
}
/*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;
}
std::error_condition JsonApiServer::restart()
{
const auto now = time(nullptr);
if(mRestartReqTS.exchange(now) + RESTART_BURST_PROTECTION > now)
return RsJsonApiErrorNum::NOT_A_MACHINE_GUN;
unProtectedRestart();
return std::error_condition();
}
void JsonApiServer::onStopRequested()
{
auto tService = std::atomic_exchange(
&mService, std::shared_ptr<rb::Service>(nullptr) );
if(tService) tService->stop();
}
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::run()
{
auto settings = std::make_shared<restbed::Settings>();
settings->set_port(mListeningPort);
settings->set_bind_address(mBindingAddress);
settings->set_default_header("Connection", "close");
auto tService = std::make_shared<restbed::Service>();
for(auto& r: getResources()) tService->publish(r);
try
{
RsUrl apiUrl; apiUrl.setScheme("http").setHost(mBindingAddress)
.setPort(mListeningPort);
RsInfo() << __PRETTY_FUNCTION__ << " JSON API server listening on "
<< apiUrl.toString() << std::endl;
/* re-allocating mService is important because it deletes the existing
* service and therefore leaves the listening port open */
auto tExpected = std::shared_ptr<rb::Service>(nullptr);
if(atomic_compare_exchange_strong(&mService, &tExpected, tService))
tService->start(settings);
else
{
RsErr() << __PRETTY_FUNCTION__ << " mService was expected to be "
<< " null, instead we got: " << tExpected
<< " something wrong happened JsonApiServer won't start"
<< std::endl;
print_stacktrace();
}
}
catch(std::exception& e)
{
RsErr() << __PRETTY_FUNCTION__ << " Failure starting JSON API server: "
<< e.what() << std::endl;
print_stacktrace();
return;
}
RsDbg() << __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;
}
std::error_condition RsJsonApiErrorCategory::default_error_condition(int ev) const noexcept
{
switch(static_cast<RsJsonApiErrorNum>(ev))
{
case RsJsonApiErrorNum::TOKEN_FORMAT_INVALID: // fallthrough
case RsJsonApiErrorNum::UNKNOWN_API_USER: // fallthrough
case RsJsonApiErrorNum::WRONG_API_PASSWORD: // fallthrough
case RsJsonApiErrorNum::AUTHORIZATION_REQUEST_DENIED:
return std::errc::permission_denied;
case RsJsonApiErrorNum::API_USER_CONTAIN_COLON:
return std::errc::invalid_argument;
default:
return std::error_condition(ev, *this);
}
}