Changed http client to be instanciated by services

This commit is contained in:
emeric
2021-12-05 15:20:57 +01:00
parent 63ed28e38f
commit c6338771cf
14 changed files with 56 additions and 69 deletions
@@ -30,6 +30,7 @@
#include "services/database/StarredTrack.hpp" #include "services/database/StarredTrack.hpp"
#include "services/database/Track.hpp" #include "services/database/Track.hpp"
#include "services/database/User.hpp" #include "services/database/User.hpp"
#include "utils/Logger.hpp"
#include "internal/InternalScrobbler.hpp" #include "internal/InternalScrobbler.hpp"
#include "listenbrainz/ListenBrainzScrobbler.hpp" #include "listenbrainz/ListenBrainzScrobbler.hpp"
@@ -47,8 +48,15 @@ namespace Scrobbling
ScrobblingService::ScrobblingService(boost::asio::io_context& ioContext, Db& db) ScrobblingService::ScrobblingService(boost::asio::io_context& ioContext, Db& db)
: _db {db} : _db {db}
{ {
LMS_LOG(SCROBBLING, INFO) << "Starting service...";
_scrobblers.emplace(Scrobbler::Internal, std::make_unique<InternalScrobbler>(_db)); _scrobblers.emplace(Scrobbler::Internal, std::make_unique<InternalScrobbler>(_db));
_scrobblers.emplace(Scrobbler::ListenBrainz, std::make_unique<ListenBrainz::Scrobbler>(ioContext, _db)); _scrobblers.emplace(Scrobbler::ListenBrainz, std::make_unique<ListenBrainz::Scrobbler>(ioContext, _db));
LMS_LOG(SCROBBLING, INFO) << "Service started!";
}
ScrobblingService::~ScrobblingService()
{
LMS_LOG(SCROBBLING, INFO) << "Service stopped!";
} }
void void
@@ -32,6 +32,7 @@ namespace Scrobbling
{ {
public: public:
ScrobblingService(boost::asio::io_context& ioContext, Database::Db& db); ScrobblingService(boost::asio::io_context& ioContext, Database::Db& db);
~ScrobblingService();
private: private:
void listenStarted(const Listen& listen) override; void listenStarted(const Listen& listen) override;
@@ -144,7 +144,8 @@ namespace Scrobbling::ListenBrainz
: _ioContext {ioContext} : _ioContext {ioContext}
, _db {db} , _db {db}
, _baseAPIUrl {Service<IConfig>::get()->getString("listenbrainz-api-base-url", "https://api.listenbrainz.org")} , _baseAPIUrl {Service<IConfig>::get()->getString("listenbrainz-api-base-url", "https://api.listenbrainz.org")}
, _listensSynchronizer {_ioContext, db, _baseAPIUrl} , _client {Http::createClient(_ioContext, _baseAPIUrl)}
, _listensSynchronizer {_ioContext, db, *_client}
{ {
LOG(INFO) << "Starting ListenBrainz scrobbler... API endpoint = '" << _baseAPIUrl; LOG(INFO) << "Starting ListenBrainz scrobbler... API endpoint = '" << _baseAPIUrl;
} }
@@ -183,7 +184,7 @@ namespace Scrobbling::ListenBrainz
Scrobbler::enqueListen(const Listen& listen, const Wt::WDateTime& timePoint) Scrobbler::enqueListen(const Listen& listen, const Wt::WDateTime& timePoint)
{ {
Http::ClientPOSTRequestParameters request; Http::ClientPOSTRequestParameters request;
request.url = _baseAPIUrl + "/1/submit-listens"; request.relativeUrl = "/1/submit-listens";
if (timePoint.isValid()) if (timePoint.isValid())
{ {
@@ -213,7 +214,7 @@ namespace Scrobbling::ListenBrainz
request.message.addBodyText(bodyText); request.message.addBodyText(bodyText);
request.message.addHeader("Authorization", "Token " + std::string {listenBrainzToken->getAsString()}); request.message.addHeader("Authorization", "Token " + std::string {listenBrainzToken->getAsString()});
request.message.addHeader("Content-Type", "application/json"); request.message.addHeader("Content-Type", "application/json");
Service<Http::IClient>::get()->sendPOSTRequest(std::move(request)); _client->sendPOSTRequest(std::move(request));
} }
} // namespace Scrobbling::ListenBrainz } // namespace Scrobbling::ListenBrainz
@@ -53,10 +53,11 @@ namespace Scrobbling::ListenBrainz
// Submit listens // Submit listens
void enqueListen(const Listen& listen, const Wt::WDateTime& timePoint); void enqueListen(const Listen& listen, const Wt::WDateTime& timePoint);
boost::asio::io_context& _ioContext; boost::asio::io_context& _ioContext;
Database::Db& _db; Database::Db& _db;
std::string _baseAPIUrl; std::string _baseAPIUrl;
ListensSynchronizer _listensSynchronizer; std::unique_ptr<Http::IClient> _client;
ListensSynchronizer _listensSynchronizer;
}; };
} // Scrobbling::ListenBrainz } // Scrobbling::ListenBrainz
@@ -213,10 +213,10 @@ namespace
namespace Scrobbling::ListenBrainz namespace Scrobbling::ListenBrainz
{ {
ListensSynchronizer::ListensSynchronizer(boost::asio::io_context& ioContext, Database::Db& db, std::string_view baseAPIUrl) ListensSynchronizer::ListensSynchronizer(boost::asio::io_context& ioContext, Database::Db& db, Http::IClient& client)
: _ioContext {ioContext} : _ioContext {ioContext}
, _db {db} , _db {db}
, _baseAPIUrl {baseAPIUrl} , _client {client}
, _maxSyncListenCount {Service<IConfig>::get()->getULong("listenbrainz-max-sync-listen-count", 1000)} , _maxSyncListenCount {Service<IConfig>::get()->getULong("listenbrainz-max-sync-listen-count", 1000)}
, _syncListensPeriod {Service<IConfig>::get()->getULong("listenbrainz-sync-listens-period-hours", 1)} , _syncListensPeriod {Service<IConfig>::get()->getULong("listenbrainz-sync-listens-period-hours", 1)}
{ {
@@ -364,7 +364,7 @@ namespace Scrobbling::ListenBrainz
Http::ClientGETRequestParameters request; Http::ClientGETRequestParameters request;
request.priority = Http::ClientRequestParameters::Priority::Low; request.priority = Http::ClientRequestParameters::Priority::Low;
request.url = _baseAPIUrl + "/1/validate-token"; request.relativeUrl = "/1/validate-token";
request.headers = { {"Authorization", "Token " + std::string {listenBrainzToken->getAsString()}} }; request.headers = { {"Authorization", "Token " + std::string {listenBrainzToken->getAsString()}} };
request.onSuccessFunc = [this, &context] (std::string_view msgBody) request.onSuccessFunc = [this, &context] (std::string_view msgBody)
{ {
@@ -381,7 +381,7 @@ namespace Scrobbling::ListenBrainz
onGetListensEnded(context); onGetListensEnded(context);
}; };
Service<Http::IClient>::get()->sendGETRequest(std::move(request)); _client.sendGETRequest(std::move(request));
} }
void void
@@ -390,7 +390,7 @@ namespace Scrobbling::ListenBrainz
assert(!context.listenBrainzUserName.empty()); assert(!context.listenBrainzUserName.empty());
Http::ClientGETRequestParameters request; Http::ClientGETRequestParameters request;
request.url = _baseAPIUrl + "/1/user/" + std::string {context.listenBrainzUserName} + "/listen-count"; request.relativeUrl = "/1/user/" + std::string {context.listenBrainzUserName} + "/listen-count";
request.priority = Http::ClientRequestParameters::Priority::Low; request.priority = Http::ClientRequestParameters::Priority::Low;
request.onSuccessFunc = [=, &context] (std::string_view msgBody) request.onSuccessFunc = [=, &context] (std::string_view msgBody)
{ {
@@ -415,7 +415,7 @@ namespace Scrobbling::ListenBrainz
onGetListensEnded(context); onGetListensEnded(context);
}; };
Service<Http::IClient>::get()->sendGETRequest(std::move(request)); _client.sendGETRequest(std::move(request));
} }
void void
@@ -424,7 +424,7 @@ namespace Scrobbling::ListenBrainz
assert(!context.listenBrainzUserName.empty()); assert(!context.listenBrainzUserName.empty());
Http::ClientGETRequestParameters request; Http::ClientGETRequestParameters request;
request.url = _baseAPIUrl + "/1/user/" + context.listenBrainzUserName + "/listens?max_ts=" + std::to_string(context.maxDateTime.toTime_t()); request.relativeUrl = "/1/user/" + context.listenBrainzUserName + "/listens?max_ts=" + std::to_string(context.maxDateTime.toTime_t());
request.priority = Http::ClientRequestParameters::Priority::Low; request.priority = Http::ClientRequestParameters::Priority::Low;
request.onSuccessFunc = [=, &context] (std::string_view msgBody) request.onSuccessFunc = [=, &context] (std::string_view msgBody)
{ {
@@ -442,7 +442,7 @@ namespace Scrobbling::ListenBrainz
onGetListensEnded(context); onGetListensEnded(context);
}; };
Service<Http::IClient>::get()->sendGETRequest(std::move(request)); _client.sendGETRequest(std::move(request));
} }
void void
@@ -36,12 +36,17 @@ namespace Database
class User; class User;
} }
namespace Http
{
class IClient;
}
namespace Scrobbling::ListenBrainz namespace Scrobbling::ListenBrainz
{ {
class ListensSynchronizer class ListensSynchronizer
{ {
public: public:
ListensSynchronizer(boost::asio::io_context& ioContext, Database::Db& db, std::string_view baseAPIUrl); ListensSynchronizer(boost::asio::io_context& ioContext, Database::Db& db, Http::IClient& client);
void saveListen(const TimedListen& listen); void saveListen(const TimedListen& listen);
@@ -81,8 +86,8 @@ namespace Scrobbling::ListenBrainz
boost::asio::io_context& _ioContext; boost::asio::io_context& _ioContext;
boost::asio::io_context::strand _strand {_ioContext}; boost::asio::io_context::strand _strand {_ioContext};
Database::Db& _db; Database::Db& _db;
std::string _baseAPIUrl;
boost::asio::steady_timer _getListensTimer {_ioContext}; boost::asio::steady_timer _getListensTimer {_ioContext};
Http::IClient& _client;
std::unordered_map<Database::UserId, UserContext> _userContexts; std::unordered_map<Database::UserId, UserContext> _userContexts;
+3 -3
View File
@@ -27,7 +27,7 @@ IOContextRunner::IOContextRunner(boost::asio::io_service& ioService, std::size_t
: _ioService {ioService} : _ioService {ioService}
, _work {ioService} , _work {ioService}
{ {
LMS_LOG(UTILS, INFO) << "Starting IO Context with " << threadCount << " threads..."; LMS_LOG(UTILS, INFO) << "Starting IO context with " << threadCount << " threads...";
for (std::size_t i {}; i < threadCount; ++i) for (std::size_t i {}; i < threadCount; ++i)
{ {
_threads.emplace_back([&] _threads.emplace_back([&]
@@ -48,10 +48,10 @@ IOContextRunner::IOContextRunner(boost::asio::io_service& ioService, std::size_t
void void
IOContextRunner::stop() IOContextRunner::stop()
{ {
LMS_LOG(UTILS, INFO) << "Stopping IO Context"; LMS_LOG(UTILS, DEBUG) << "Stopping IO context...";
_work.reset(); _work.reset();
_ioService.stop(); _ioService.stop();
LMS_LOG(UTILS, INFO) << "Stopped IO Context"; LMS_LOG(UTILS, DEBUG) << "IO context stopped!";
} }
IOContextRunner::~IOContextRunner() IOContextRunner::~IOContextRunner()
+4 -34
View File
@@ -23,51 +23,21 @@
namespace Http namespace Http
{ {
std::unique_ptr<IClient> std::unique_ptr<IClient>
createClient(boost::asio::io_context& ioContext) createClient(boost::asio::io_context& ioContext, std::string_view baseUrl)
{ {
return std::make_unique<Client>(ioContext); return std::make_unique<Client>(ioContext, baseUrl);
} }
void void
Client::sendGETRequest(ClientGETRequestParameters&& GETParams) Client::sendGETRequest(ClientGETRequestParameters&& GETParams)
{ {
SendQueue& sendQueue {getOrCreateSendQueue(GETParams.url)}; _sendQueue.sendRequest(std::make_unique<ClientRequest>(std::move(GETParams)));
sendQueue.sendRequest(std::make_unique<ClientRequest>(std::move(GETParams)));
} }
void void
Client::sendPOSTRequest(ClientPOSTRequestParameters&& POSTParams) Client::sendPOSTRequest(ClientPOSTRequestParameters&& POSTParams)
{ {
SendQueue& sendQueue {getOrCreateSendQueue(POSTParams.url)}; _sendQueue.sendRequest(std::make_unique<ClientRequest>(std::move(POSTParams)));
sendQueue.sendRequest(std::make_unique<ClientRequest>(std::move(POSTParams)));
} }
SendQueue&
Client::getOrCreateSendQueue(const std::string& url)
{
Wt::Http::Client::URL parsedURL;
if (!Wt::Http::Client::parseUrl(url, parsedURL))
throw LmsException {"Cannot parse URL '" + url + "'"};
{
std::shared_lock lock {_sendQueuesMutex};
if (auto it = _sendQueues.find(parsedURL.host); it != std::cend(_sendQueues))
return it->second;
}
{
std::unique_lock lock {_sendQueuesMutex};
if (auto it = _sendQueues.find(parsedURL.host); it != std::cend(_sendQueues))
return it->second;
auto [it, inserted] {_sendQueues.emplace(parsedURL.host, _ioContext)};
assert(inserted);
return it->second;
}
}
} // namespace Http } // namespace Http
+6 -6
View File
@@ -31,17 +31,17 @@ namespace Http
class Client final : public IClient class Client final : public IClient
{ {
public: public:
Client(boost::asio::io_context& ioContext) : _ioContext {ioContext} {} Client(boost::asio::io_context& ioContext, std::string_view baseUrl)
: _ioContext {ioContext}
, _sendQueue {ioContext, baseUrl}
{}
private: private:
void sendGETRequest(ClientGETRequestParameters&& request) override; void sendGETRequest(ClientGETRequestParameters&& request) override;
void sendPOSTRequest(ClientPOSTRequestParameters&& request) override; void sendPOSTRequest(ClientPOSTRequestParameters&& request) override;
SendQueue& getOrCreateSendQueue(const std::string& host); boost::asio::io_context& _ioContext;
SendQueue _sendQueue;
boost::asio::io_context& _ioContext;
std::shared_mutex _sendQueuesMutex;
std::unordered_map<std::string, SendQueue> _sendQueues;
}; };
} // namespace Http } // namespace Http
+6 -4
View File
@@ -60,8 +60,9 @@ namespace
namespace Http namespace Http
{ {
SendQueue::SendQueue(boost::asio::io_context& ioContext) SendQueue::SendQueue(boost::asio::io_context& ioContext, std::string_view baseUrl)
: _ioContext {ioContext} : _ioContext {ioContext}
, _baseUrl {baseUrl}
{ {
_client.done().connect([this](Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg) _client.done().connect([this](Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg)
{ {
@@ -116,17 +117,18 @@ namespace Http
bool bool
SendQueue::sendRequest(const ClientRequest& request) SendQueue::sendRequest(const ClientRequest& request)
{ {
LOG(DEBUG) << "Sending request to url '" << request.getParameters().url << "'"; std::string url {_baseUrl + request.getParameters().relativeUrl};
LOG(DEBUG) << "Sending request to url '" << url << "'";
bool res {}; bool res {};
switch (request.getType()) switch (request.getType())
{ {
case ClientRequest::Type::GET: case ClientRequest::Type::GET:
res = _client.get(request.getParameters().url, request.getGETParameters().headers); res = _client.get(url, request.getGETParameters().headers);
break; break;
case ClientRequest::Type::POST: case ClientRequest::Type::POST:
res = _client.post(request.getParameters().url, request.getPOSTParameters().message); res = _client.post(url, request.getPOSTParameters().message);
break; break;
} }
+2 -1
View File
@@ -35,7 +35,7 @@ namespace Http
class SendQueue class SendQueue
{ {
public: public:
SendQueue(boost::asio::io_context& ioContext); SendQueue(boost::asio::io_context& ioContext, std::string_view baseUrl);
~SendQueue(); ~SendQueue();
SendQueue(const SendQueue&) = delete; SendQueue(const SendQueue&) = delete;
@@ -62,6 +62,7 @@ namespace Http
boost::asio::io_context& _ioContext; boost::asio::io_context& _ioContext;
boost::asio::io_context::strand _strand {_ioContext}; boost::asio::io_context::strand _strand {_ioContext};
boost::asio::steady_timer _throttleTimer {_ioContext}; boost::asio::steady_timer _throttleTimer {_ioContext};
std::string _baseUrl;
enum class State enum class State
{ {
@@ -37,7 +37,7 @@ namespace Http
}; };
Priority priority {Priority::Normal}; Priority priority {Priority::Normal};
std::string url; std::string relativeUrl; // relative to baseUrl used by the client
using OnSuccessFunc = std::function<void(std::string_view msgBody)>; using OnSuccessFunc = std::function<void(std::string_view msgBody)>;
OnSuccessFunc onSuccessFunc; OnSuccessFunc onSuccessFunc;
@@ -35,6 +35,6 @@ namespace Http
virtual void sendPOSTRequest(ClientPOSTRequestParameters&& request) = 0; virtual void sendPOSTRequest(ClientPOSTRequestParameters&& request) = 0;
}; };
std::unique_ptr<IClient> createClient(boost::asio::io_context& ioContext); std::unique_ptr<IClient> createClient(boost::asio::io_context& ioContext, std::string_view baseUrl);
} // namespace Http } // namespace Http
-2
View File
@@ -37,7 +37,6 @@
#include "subsonic/SubsonicResource.hpp" #include "subsonic/SubsonicResource.hpp"
#include "ui/LmsApplication.hpp" #include "ui/LmsApplication.hpp"
#include "ui/LmsApplicationManager.hpp" #include "ui/LmsApplicationManager.hpp"
#include "utils/http/IClient.hpp"
#include "utils/IChildProcessManager.hpp" #include "utils/IChildProcessManager.hpp"
#include "utils/IConfig.hpp" #include "utils/IConfig.hpp"
#include "utils/IOContextRunner.hpp" #include "utils/IOContextRunner.hpp"
@@ -255,7 +254,6 @@ int main(int argc, char* argv[])
else else
throw LmsException {"Bad value '" + authenticationBackend + "' for 'authentication-backend'"}; throw LmsException {"Bad value '" + authenticationBackend + "' for 'authentication-backend'"};
Service<Http::IClient> httpClient {Http::createClient(ioContext)};
Service<Cover::ICoverService> coverService {Cover::createCoverService(database, argv[0], server.appRoot() + "/images/unknown-cover.jpg")}; Service<Cover::ICoverService> coverService {Cover::createCoverService(database, argv[0], server.appRoot() + "/images/unknown-cover.jpg")};
Service<Recommendation::IRecommendationService> recommendationService {Recommendation::createRecommendationService(database)}; Service<Recommendation::IRecommendationService> recommendationService {Recommendation::createRecommendationService(database)};
Service<Scanner::IScannerService> scannerService {Scanner::createScannerService(database, *recommendationService)}; Service<Scanner::IScannerService> scannerService {Scanner::createScannerService(database, *recommendationService)};