This commit is contained in:
defnax 2026-09-04 14:46:22 +02:00 committed by GitHub
commit 9d5dc36d45
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 120 additions and 18 deletions

View File

@ -155,6 +155,7 @@ bool RsJsonApi::parseToken(
}
JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"),
mResourceProvidersMutex("JsonApiServer resource providers"),
mService(nullptr),
mListeningPort(RsJsonApi::DEFAULT_PORT),
mBindingAddress(RsJsonApi::DEFAULT_BINDING_ADDRESS),
@ -836,7 +837,10 @@ void JsonApiServer::handleCorsOptions(
void JsonApiServer::registerResourceProvider(const JsonApiResourceProvider& rp)
{
mResourceProviders.insert(rp);
{
RS_STACK_MUTEX(mResourceProvidersMutex);
mResourceProviders.insert(rp);
}
if(rsEvents)
{
@ -847,7 +851,10 @@ void JsonApiServer::registerResourceProvider(const JsonApiResourceProvider& rp)
}
void JsonApiServer::unregisterResourceProvider(const JsonApiResourceProvider& rp)
{
mResourceProviders.erase(rp);
{
RS_STACK_MUTEX(mResourceProvidersMutex);
mResourceProviders.erase(rp);
}
if(rsEvents)
{
@ -857,10 +864,14 @@ void JsonApiServer::unregisterResourceProvider(const JsonApiResourceProvider& rp
}
}
bool JsonApiServer::hasResourceProvider(const JsonApiResourceProvider& rp)
{ return mResourceProviders.find(rp) != mResourceProviders.end(); }
const std::set<std::reference_wrapper<const JsonApiResourceProvider>,std::less<const JsonApiResourceProvider> >& JsonApiServer::getResourceProviders() const
{
RS_STACK_MUTEX(mResourceProvidersMutex);
return mResourceProviders.find(rp) != mResourceProviders.end();
}
RsJsonApi::ResourceProviderSet JsonApiServer::getResourceProviders() const
{
RS_STACK_MUTEX(mResourceProvidersMutex);
return mResourceProviders;
}
@ -868,7 +879,7 @@ std::vector<std::shared_ptr<rb::Resource> > JsonApiServer::getResources() const
{
auto tab = mResources;
for(auto& rp: mResourceProviders)
for(auto& rp: getResourceProviders())
for(auto r: rp.get().getResources()) tab.push_back(r);
return tab;

View File

@ -64,8 +64,8 @@ public:
std::vector<std::shared_ptr<rb::Resource>> getResources() const;
/// @see RsJsonApi
const std::set<std::reference_wrapper<const JsonApiResourceProvider>,std::less<const JsonApiResourceProvider> >& getResourceProviders() const override;
/// @see RsJsonApi
ResourceProviderSet getResourceProviders() const override;
/// @see RsJsonApi
void fullstop() override { RsThread::fullstop(); }
@ -180,6 +180,7 @@ private:
/// Encrypted persistent storage for authorized JSON API tokens
JsonApiServerAuthTokenStorage mAuthTokenStorage;
RsMutex configMutex;
mutable RsMutex mResourceProvidersMutex;
static const std::multimap<std::string, std::string> corsHeaders;
static const std::multimap<std::string, std::string> corsOptionsHeaders;

View File

@ -371,7 +371,17 @@ bool RsPluginManager::loadPlugin(const std::string& plugin_name,bool first_time)
pinfo.info_string = "" ;
dlclose(handle);
return false ;
}
}
if(pinfo.API_version != RS_PLUGIN_API_VERSION)
{
std::cerr << " -> Incompatible plugin API version "
<< std::hex << pinfo.API_version << "; expected "
<< RS_PLUGIN_API_VERSION << std::dec << std::endl;
pinfo.status = PLUGIN_STATUS_WRONG_API;
pinfo.info_string = "Incompatible plugin API version";
dlclose(handle);
return false;
}
#ifdef TO_REMOVE
if(pinfo.svn_revision == 0)
{

View File

@ -37,6 +37,11 @@ class RsJsonApi;
/**
* Pointer to global instance of RsJsonApi service implementation
* @jsonapi{development}
*
* Beware that non-null does not imply configured: RsServer::StartupRetroShare()
* creates the server early so plugins can register resource providers, while
* the config manager, the authorized tokens, the listening port and the binding
* address are only applied later by RsInit::startupWebServices().
*/
extern RsJsonApi* rsJsonApi;
@ -203,15 +208,21 @@ public:
*/
virtual void connectToConfigManager(p3ConfigMgr& cfgmgr) = 0;
using ResourceProviderSet = std::set<
std::reference_wrapper<const JsonApiResourceProvider>,
std::less<const JsonApiResourceProvider> >;
/**
* This is used to add/remove new web services to JsonAPI. The client
* should take care of not using a path range already used by the jsonAPI
* server
* server.
* @see getResourceProviders() returns a snapshot by value, so it stays
* valid while another thread registers or unregisters a provider.
*/
virtual void registerResourceProvider(const JsonApiResourceProvider&) = 0;
virtual void unregisterResourceProvider(const JsonApiResourceProvider&) = 0;
virtual bool hasResourceProvider(const JsonApiResourceProvider&) = 0;
virtual const std::set<std::reference_wrapper<const JsonApiResourceProvider>,std::less<const JsonApiResourceProvider> >& getResourceProviders() const =0;
virtual ResourceProviderSet getResourceProviders() const = 0;
/**
* @brief This function should be used by JSON API clients that aren't

View File

@ -38,6 +38,7 @@ extern RsPluginHandler *rsPlugins ;
class p3Service ;
class RsServiceControl ;
class RsReputations ;
class RsJsonApi;
class RsTurtle ;
class RsGxsTunnelService ;
class RsDht ;
@ -87,10 +88,10 @@ namespace resource_api
class StateTokenServer;
}
// Plugin API version. Not used yet, but will be in the future the
// main value that decides for compatibility.
// Plugin API version. RsPluginManager rejects plugins built against a
// different version before calling into their ABI.
//
#define RS_PLUGIN_API_VERSION 0x000101
#define RS_PLUGIN_API_VERSION 0x000103
// Used for the status of plugins.
//
@ -141,6 +142,9 @@ public:
#ifdef RS_USE_WIRE
RsUtil::inited_ptr<RsWire> mWire;
#endif
// Keep new members appended so offsets used by older plugins do not move.
RsUtil::inited_ptr<RsJsonApi> mJsonApi;
};
class RsPlugin

View File

@ -445,7 +445,24 @@ void RsInit::startupWebServices(const RsConfigOptions& conf,bool force_start_jso
std::cerr << std::endl;
RsInfo() << "Configuring web services" ;
JsonApiServer* jas = new JsonApiServer();
// Plugins receive RsPlugInInterfaces during core startup, before the GUI
// calls startupWebServices(). Reuse the server created for that handoff so
// resource providers registered by plugins are not lost here.
JsonApiServer* jas = dynamic_cast<JsonApiServer*>(rsJsonApi);
if(!jas)
{
/* Not reachable today -- rsJsonApi is only ever set to a JsonApiServer,
* here and in the plugin handoff of StartupRetroShare(). Should that
* change, replacing it silently would leave every plugin holding the
* pointer it received in setInterfaces() talking to an abandoned
* object, with no trace of why. */
if(rsJsonApi)
RsErr() << "rsJsonApi is set but is not a JsonApiServer. Replacing "
<< "it: plugins still hold the previous pointer.";
jas = new JsonApiServer();
rsJsonApi = jas;
}
bool jsonapi_needed = force_start_jsonapi;
// add jsonapi server to config manager so that it can save/load its tokens
@ -529,8 +546,6 @@ void RsInit::startupWebServices(const RsConfigOptions& conf,bool force_start_jso
}
else
RsInfo() << " Not starting JSON API, since it is currently not required by any service." ;
rsJsonApi = jas;
}
#endif
@ -1675,6 +1690,22 @@ int RsServer::StartupRetroShare()
#endif
interfaces.mServiceControl = serviceCtrl;
interfaces.mPluginHandler = mPluginsManager;
#ifdef RS_JSONAPI
// The GUI configures web services after plugins are initialized. Create
// the server now so plugins can register providers in setInterfaces().
// startupWebServices() will configure and reuse this same instance later.
//
// This has to stay *after* the "if (rsJsonApi) connectToConfigManager()"
// block above: that block exists for retroshare-service and Android, where
// startupWebServices() already ran before login. Creating the server before
// it would make the GUI connect the config manager here and again in
// startupWebServices(), reloading jsonapi.cfg twice.
if(!rsJsonApi)
rsJsonApi = new JsonApiServer();
interfaces.mJsonApi = rsJsonApi;
#else
interfaces.mJsonApi = nullptr;
#endif
// gxs
interfaces.mGxsDir = currGxsDir;
interfaces.mIdentity = mGxsIdService;
@ -1690,8 +1721,42 @@ int RsServer::StartupRetroShare()
interfaces.mGxsTunnels = mGxsTunnels;
interfaces.mReputations = mReputations;
interfaces.mPosted = mPosted;
#ifdef RS_JSONAPI
// Service and Android start JSON API before plugins are initialized. Take
// one snapshot here so all newly registered providers are published with
// at most one core-owned restart after every plugin received interfaces.
const bool jsonApiWasRunning = rsJsonApi && rsJsonApi->isRunning();
const std::size_t jsonApiProviderCount = jsonApiWasRunning ?
rsJsonApi->getResourceProviders().size() : 0;
#endif
mPluginsManager->setInterfaces(interfaces);
#ifdef RS_JSONAPI
if( jsonApiWasRunning && jsonApiProviderCount !=
rsJsonApi->getResourceProviders().size() )
{
RsInfo() << "Restarting JSON API once to publish plugin resources.";
/* The restart must not run on the JSON API server thread.
* StartupRetroShare() is reached from RsLoginHelper::attemptLogin() and
* createLocationV2(), both exposed through the JSON API, and restbed
* serves its handlers on the thread that called Service::start(), which
* is JsonApiServer::run() itself. restart() calls RsThread::fullstop(),
* and joining our own thread makes waitWhileStopping() bail out with an
* error instead of waiting; the subsequent RsThread::start() then fails
* with "attempt to start already running thread" and the server stays
* down for good. The /rsJsonApi/restart handler takes the same care.
* Dispatching also keeps the RESTART_BURST_PROTECTION wait, which is
* meant to throttle API clients, off the startup path. */
RsThread::async([]()
{
const std::error_condition restartError = rsJsonApi->restart(true);
if(restartError)
RsErr() << "Failed restarting JSON API after plugin "
<< "initialization: " << restartError.message();
});
}
#endif
// now add plugin objects inside the loop:
// - client services provided by plugins.