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.
pull/8/head
Lioncash 7 years ago
parent a7725d354c
commit 183a664405

@ -5,6 +5,7 @@
#pragma once #pragma once
#include <string> #include <string>
#include "common/common_types.h"
namespace Common { namespace Common {
struct WebResult { struct WebResult {

@ -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,9 +3,11 @@
// 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 "common/common_types.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/web_result.h" #include "common/web_result.h"
#include "core/settings.h" #include "core/settings.h"
@ -20,19 +22,47 @@ constexpr u32 HTTPS_PORT = 443;
constexpr u32 TIMEOUT_SECONDS = 30; constexpr u32 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;
} }
} }
Common::WebResult Client::GenericJson(const std::string& method, const std::string& path, /// A generic function handles POST, GET and DELETE request together
const std::string& data, const std::string& jwt, Common::WebResult GenericJson(const std::string& method, const std::string& path,
const std::string& username, const std::string& token) { const std::string& data, bool allow_anonymous) {
if (jwt.empty()) {
UpdateJWT();
}
if (jwt.empty() && !allow_anonymous) {
LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
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;
@ -40,8 +70,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;
@ -70,7 +100,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,8 +142,12 @@ Common::WebResult Client::GenericJson(const std::string& method, const std::stri
return Common::WebResult{Common::WebResult::Code::Success, "", response.body}; return Common::WebResult{Common::WebResult::Code::Success, "", response.body};
} }
void Client::UpdateJWT() { // Retrieve a new JWT from given username and token
if (!username.empty() && !token.empty()) { void UpdateJWT() {
if (username.empty() || token.empty()) {
return;
}
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");
@ -123,27 +158,39 @@ void Client::UpdateJWT() {
jwt_cache.jwt = jwt = result.returned_data; jwt_cache.jwt = jwt = result.returned_data;
} }
} }
}
Common::WebResult Client::GenericJson(const std::string& method, const std::string& path, std::string host;
const std::string& data, bool allow_anonymous) { std::string username;
if (jwt.empty()) { std::string token;
UpdateJWT(); std::string jwt;
} std::unique_ptr<httplib::Client> cli;
if (jwt.empty() && !allow_anonymous) { struct JWTCache {
LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); std::mutex mutex;
return Common::WebResult{Common::WebResult::Code::CredentialsMissing, "Credentials needed"}; 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);
} }
auto result = GenericJson(method, path, data, jwt); Common::WebResult Client::GetJson(const std::string& path, bool allow_anonymous) {
if (result.result_string == "401") { return impl->GenericJson("GET", path, "", allow_anonymous);
// Try again with new JWT
UpdateJWT();
result = GenericJson(method, path, data, jwt);
} }
return result; Common::WebResult Client::DeleteJson(const std::string& path, const std::string& data,
bool allow_anonymous) {
return impl->GenericJson("DELETE", path, data, allow_anonymous);
} }
} // namespace WebService } // namespace WebService

@ -4,23 +4,19 @@
#pragma once #pragma once
#include <functional> #include <memory>
#include <mutex>
#include <string> #include <string>
#include <tuple>
#include <httplib.h>
#include "common/common_types.h"
#include "common/web_result.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();
/** /**
* Posts JSON to the specified path. * Posts JSON to the specified path.
@ -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…
Cancel
Save