web_backend: Make Client use the PImpl idiom
Like with TelemetryJson, we can make the implementation details private and avoid the need to expose httplib to external libraries that need to use the Client class.
This commit is contained in:
parent
25038aeb0d
commit
8b98560ebb
4 changed files with 161 additions and 153 deletions
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include <json.hpp>
|
#include <json.hpp>
|
||||||
#include "common/detached_tasks.h"
|
#include "common/detached_tasks.h"
|
||||||
|
#include "common/web_result.h"
|
||||||
#include "web_service/telemetry_json.h"
|
#include "web_service/telemetry_json.h"
|
||||||
#include "web_service/web_backend.h"
|
#include "web_service/web_backend.h"
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <json.hpp>
|
#include <json.hpp>
|
||||||
|
#include "common/web_result.h"
|
||||||
#include "web_service/verify_login.h"
|
#include "web_service/verify_login.h"
|
||||||
#include "web_service/web_backend.h"
|
#include "web_service/web_backend.h"
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
|
||||||
#include <LUrlParser.h>
|
#include <LUrlParser.h>
|
||||||
#include <httplib.h>
|
#include <httplib.h>
|
||||||
#include "common/announce_multiplayer_room.h"
|
#include "common/announce_multiplayer_room.h"
|
||||||
|
#include "common/common_types.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "web_service/web_backend.h"
|
#include "web_service/web_backend.h"
|
||||||
|
|
||||||
|
@ -20,21 +21,47 @@ constexpr int HTTPS_PORT = 443;
|
||||||
|
|
||||||
constexpr std::size_t TIMEOUT_SECONDS = 30;
|
constexpr std::size_t TIMEOUT_SECONDS = 30;
|
||||||
|
|
||||||
Client::JWTCache Client::jwt_cache{};
|
struct Client::Impl {
|
||||||
|
Impl(std::string host, std::string username, std::string token)
|
||||||
Client::Client(const std::string& host, const std::string& username, const std::string& token)
|
: host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {
|
||||||
: host(host), username(username), token(token) {
|
|
||||||
std::lock_guard<std::mutex> lock(jwt_cache.mutex);
|
std::lock_guard<std::mutex> lock(jwt_cache.mutex);
|
||||||
if (username == jwt_cache.username && token == jwt_cache.token) {
|
if (this->username == jwt_cache.username && this->token == jwt_cache.token) {
|
||||||
jwt = jwt_cache.jwt;
|
jwt = jwt_cache.jwt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Client::~Client() = default;
|
/// A generic function handles POST, GET and DELETE request together
|
||||||
|
Common::WebResult GenericJson(const std::string& method, const std::string& path,
|
||||||
|
const std::string& data, bool allow_anonymous) {
|
||||||
|
if (jwt.empty()) {
|
||||||
|
UpdateJWT();
|
||||||
|
}
|
||||||
|
|
||||||
Common::WebResult Client::GenericJson(const std::string& method, const std::string& path,
|
if (jwt.empty() && !allow_anonymous) {
|
||||||
const std::string& data, const std::string& jwt,
|
LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
|
||||||
const std::string& username, const std::string& token) {
|
return Common::WebResult{Common::WebResult::Code::CredentialsMissing,
|
||||||
|
"Credentials needed"};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = GenericJson(method, path, data, jwt);
|
||||||
|
if (result.result_string == "401") {
|
||||||
|
// Try again with new JWT
|
||||||
|
UpdateJWT();
|
||||||
|
result = GenericJson(method, path, data, jwt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic function with explicit authentication method specified
|
||||||
|
* JWT is used if the jwt parameter is not empty
|
||||||
|
* username + token is used if jwt is empty but username and token are
|
||||||
|
* not empty anonymous if all of jwt, username and token are empty
|
||||||
|
*/
|
||||||
|
Common::WebResult GenericJson(const std::string& method, const std::string& path,
|
||||||
|
const std::string& data, const std::string& jwt = "",
|
||||||
|
const std::string& username = "", const std::string& token = "") {
|
||||||
if (cli == nullptr) {
|
if (cli == nullptr) {
|
||||||
auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
|
auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
|
||||||
int port;
|
int port;
|
||||||
|
@ -42,8 +69,8 @@ Common::WebResult Client::GenericJson(const std::string& method, const std::stri
|
||||||
if (!parsedUrl.GetPort(&port)) {
|
if (!parsedUrl.GetPort(&port)) {
|
||||||
port = HTTP_PORT;
|
port = HTTP_PORT;
|
||||||
}
|
}
|
||||||
cli =
|
cli = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port,
|
||||||
std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port, TIMEOUT_SECONDS);
|
TIMEOUT_SECONDS);
|
||||||
} else if (parsedUrl.m_Scheme == "https") {
|
} else if (parsedUrl.m_Scheme == "https") {
|
||||||
if (!parsedUrl.GetPort(&port)) {
|
if (!parsedUrl.GetPort(&port)) {
|
||||||
port = HTTPS_PORT;
|
port = HTTPS_PORT;
|
||||||
|
@ -72,7 +99,8 @@ Common::WebResult Client::GenericJson(const std::string& method, const std::stri
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
params.emplace(std::string("api-version"), std::string(API_VERSION.begin(), API_VERSION.end()));
|
params.emplace(std::string("api-version"),
|
||||||
|
std::string(API_VERSION.begin(), API_VERSION.end()));
|
||||||
if (method != "GET") {
|
if (method != "GET") {
|
||||||
params.emplace(std::string("Content-Type"), std::string("application/json"));
|
params.emplace(std::string("Content-Type"), std::string("application/json"));
|
||||||
};
|
};
|
||||||
|
@ -111,10 +139,14 @@ Common::WebResult Client::GenericJson(const std::string& method, const std::stri
|
||||||
return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"};
|
return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"};
|
||||||
}
|
}
|
||||||
return Common::WebResult{Common::WebResult::Code::Success, "", response.body};
|
return Common::WebResult{Common::WebResult::Code::Success, "", response.body};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve a new JWT from given username and token
|
||||||
|
void UpdateJWT() {
|
||||||
|
if (username.empty() || token.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
void Client::UpdateJWT() {
|
|
||||||
if (!username.empty() && !token.empty()) {
|
|
||||||
auto result = GenericJson("POST", "/jwt/internal", "", "", username, token);
|
auto result = GenericJson("POST", "/jwt/internal", "", "", username, token);
|
||||||
if (result.result_code != Common::WebResult::Code::Success) {
|
if (result.result_code != Common::WebResult::Code::Success) {
|
||||||
LOG_ERROR(WebService, "UpdateJWT failed");
|
LOG_ERROR(WebService, "UpdateJWT failed");
|
||||||
|
@ -125,27 +157,39 @@ void Client::UpdateJWT() {
|
||||||
jwt_cache.jwt = jwt = result.returned_data;
|
jwt_cache.jwt = jwt = result.returned_data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string host;
|
||||||
|
std::string username;
|
||||||
|
std::string token;
|
||||||
|
std::string jwt;
|
||||||
|
std::unique_ptr<httplib::Client> cli;
|
||||||
|
|
||||||
|
struct JWTCache {
|
||||||
|
std::mutex mutex;
|
||||||
|
std::string username;
|
||||||
|
std::string token;
|
||||||
|
std::string jwt;
|
||||||
|
};
|
||||||
|
static inline JWTCache jwt_cache;
|
||||||
|
};
|
||||||
|
|
||||||
|
Client::Client(std::string host, std::string username, std::string token)
|
||||||
|
: impl{std::make_unique<Impl>(std::move(host), std::move(username), std::move(token))} {}
|
||||||
|
|
||||||
|
Client::~Client() = default;
|
||||||
|
|
||||||
|
Common::WebResult Client::PostJson(const std::string& path, const std::string& data,
|
||||||
|
bool allow_anonymous) {
|
||||||
|
return impl->GenericJson("POST", path, data, allow_anonymous);
|
||||||
}
|
}
|
||||||
|
|
||||||
Common::WebResult Client::GenericJson(const std::string& method, const std::string& path,
|
Common::WebResult Client::GetJson(const std::string& path, bool allow_anonymous) {
|
||||||
const std::string& data, bool allow_anonymous) {
|
return impl->GenericJson("GET", path, "", allow_anonymous);
|
||||||
if (jwt.empty()) {
|
}
|
||||||
UpdateJWT();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jwt.empty() && !allow_anonymous) {
|
Common::WebResult Client::DeleteJson(const std::string& path, const std::string& data,
|
||||||
LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
|
bool allow_anonymous) {
|
||||||
return Common::WebResult{Common::WebResult::Code::CredentialsMissing, "Credentials needed"};
|
return impl->GenericJson("DELETE", path, data, allow_anonymous);
|
||||||
}
|
|
||||||
|
|
||||||
auto result = GenericJson(method, path, data, jwt);
|
|
||||||
if (result.result_string == "401") {
|
|
||||||
// Try again with new JWT
|
|
||||||
UpdateJWT();
|
|
||||||
result = GenericJson(method, path, data, jwt);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace WebService
|
} // namespace WebService
|
||||||
|
|
|
@ -4,22 +4,18 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <functional>
|
#include <memory>
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <tuple>
|
|
||||||
#include "common/announce_multiplayer_room.h"
|
|
||||||
#include "common/common_types.h"
|
|
||||||
|
|
||||||
namespace httplib {
|
namespace Common {
|
||||||
class Client;
|
struct WebResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace WebService {
|
namespace WebService {
|
||||||
|
|
||||||
class Client {
|
class Client {
|
||||||
public:
|
public:
|
||||||
Client(const std::string& host, const std::string& username, const std::string& token);
|
Client(std::string host, std::string username, std::string token);
|
||||||
~Client();
|
~Client();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,9 +26,7 @@ public:
|
||||||
* @return the result of the request.
|
* @return the result of the request.
|
||||||
*/
|
*/
|
||||||
Common::WebResult PostJson(const std::string& path, const std::string& data,
|
Common::WebResult PostJson(const std::string& path, const std::string& data,
|
||||||
bool allow_anonymous) {
|
bool allow_anonymous);
|
||||||
return GenericJson("POST", path, data, allow_anonymous);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets JSON from the specified path.
|
* Gets JSON from the specified path.
|
||||||
|
@ -40,9 +34,7 @@ public:
|
||||||
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
|
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
|
||||||
* @return the result of the request.
|
* @return the result of the request.
|
||||||
*/
|
*/
|
||||||
Common::WebResult GetJson(const std::string& path, bool allow_anonymous) {
|
Common::WebResult GetJson(const std::string& path, bool allow_anonymous);
|
||||||
return GenericJson("GET", path, "", allow_anonymous);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes JSON to the specified path.
|
* Deletes JSON to the specified path.
|
||||||
|
@ -52,41 +44,11 @@ public:
|
||||||
* @return the result of the request.
|
* @return the result of the request.
|
||||||
*/
|
*/
|
||||||
Common::WebResult DeleteJson(const std::string& path, const std::string& data,
|
Common::WebResult DeleteJson(const std::string& path, const std::string& data,
|
||||||
bool allow_anonymous) {
|
bool allow_anonymous);
|
||||||
return GenericJson("DELETE", path, data, allow_anonymous);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// A generic function handles POST, GET and DELETE request together
|
struct Impl;
|
||||||
Common::WebResult GenericJson(const std::string& method, const std::string& path,
|
std::unique_ptr<Impl> impl;
|
||||||
const std::string& data, bool allow_anonymous);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A generic function with explicit authentication method specified
|
|
||||||
* JWT is used if the jwt parameter is not empty
|
|
||||||
* username + token is used if jwt is empty but username and token are not empty
|
|
||||||
* anonymous if all of jwt, username and token are empty
|
|
||||||
*/
|
|
||||||
Common::WebResult GenericJson(const std::string& method, const std::string& path,
|
|
||||||
const std::string& data, const std::string& jwt = "",
|
|
||||||
const std::string& username = "", const std::string& token = "");
|
|
||||||
|
|
||||||
// Retrieve a new JWT from given username and token
|
|
||||||
void UpdateJWT();
|
|
||||||
|
|
||||||
std::string host;
|
|
||||||
std::string username;
|
|
||||||
std::string token;
|
|
||||||
std::string jwt;
|
|
||||||
std::unique_ptr<httplib::Client> cli;
|
|
||||||
|
|
||||||
struct JWTCache {
|
|
||||||
std::mutex mutex;
|
|
||||||
std::string username;
|
|
||||||
std::string token;
|
|
||||||
std::string jwt;
|
|
||||||
};
|
|
||||||
static JWTCache jwt_cache;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace WebService
|
} // namespace WebService
|
||||||
|
|
Loading…
Reference in a new issue