web_service: stop using std::future + callback style async

This commit is contained in:
Weiyi Wang 2018-09-12 12:22:48 -04:00
parent 0a4d338ffa
commit 77c1f647cb
23 changed files with 329 additions and 457 deletions

View file

@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <future>
#include "common/detached_tasks.h"
#include "common/logging/log.h"
#include "web_service/announce_room_json.h"
#include "web_service/json.h"
@ -82,30 +83,31 @@ void RoomJson::AddPlayer(const std::string& nickname,
room.members.push_back(member);
}
std::future<Common::WebResult> RoomJson::Announce() {
Common::WebResult RoomJson::Announce() {
nlohmann::json json = room;
return PostJson(endpoint_url, json.dump(), false);
return client.PostJson("/lobby", json.dump(), false);
}
void RoomJson::ClearPlayers() {
room.members.clear();
}
std::future<AnnounceMultiplayerRoom::RoomList> RoomJson::GetRoomList(std::function<void()> func) {
auto DeSerialize = [func](const std::string& reply) -> AnnounceMultiplayerRoom::RoomList {
nlohmann::json json = nlohmann::json::parse(reply);
AnnounceMultiplayerRoom::RoomList room_list =
json.at("rooms").get<AnnounceMultiplayerRoom::RoomList>();
func();
return room_list;
};
return GetJson<AnnounceMultiplayerRoom::RoomList>(DeSerialize, endpoint_url, true);
AnnounceMultiplayerRoom::RoomList RoomJson::GetRoomList() {
auto reply = client.GetJson("/lobby", true).returned_data;
if (reply.empty()) {
return {};
}
return nlohmann::json::parse(reply).at("rooms").get<AnnounceMultiplayerRoom::RoomList>();
}
void RoomJson::Delete() {
nlohmann::json json;
json["id"] = room.UID;
DeleteJson(endpoint_url, json.dump());
Common::DetachedTasks::AddTask(
[host{this->host}, username{this->username}, token{this->token}, content{json.dump()}]() {
// create a new client here because the this->client might be destroyed.
Client{host, username, token}.DeleteJson("/lobby", content, false);
});
}
} // namespace WebService

View file

@ -5,9 +5,9 @@
#pragma once
#include <functional>
#include <future>
#include <string>
#include "common/announce_multiplayer_room.h"
#include "web_service/web_backend.h"
namespace WebService {
@ -17,8 +17,8 @@ namespace WebService {
*/
class RoomJson : public AnnounceMultiplayerRoom::Backend {
public:
RoomJson(const std::string& endpoint_url, const std::string& username, const std::string& token)
: endpoint_url(endpoint_url), username(username), token(token) {}
RoomJson(const std::string& host, const std::string& username, const std::string& token)
: client(host, username, token), host(host), username(username), token(token) {}
~RoomJson() = default;
void SetRoomInformation(const std::string& uid, const std::string& name, const u16 port,
const u32 max_player, const u32 net_version, const bool has_password,
@ -27,14 +27,15 @@ public:
void AddPlayer(const std::string& nickname,
const AnnounceMultiplayerRoom::MacAddress& mac_address, const u64 game_id,
const std::string& game_name) override;
std::future<Common::WebResult> Announce() override;
Common::WebResult Announce() override;
void ClearPlayers() override;
std::future<AnnounceMultiplayerRoom::RoomList> GetRoomList(std::function<void()> func) override;
AnnounceMultiplayerRoom::RoomList GetRoomList() override;
void Delete() override;
private:
AnnounceMultiplayerRoom::Room room;
std::string endpoint_url;
Client client;
std::string host;
std::string username;
std::string token;
};

View file

@ -2,7 +2,9 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <thread>
#include "common/assert.h"
#include "common/detached_tasks.h"
#include "web_service/telemetry_json.h"
#include "web_service/web_backend.h"
@ -81,8 +83,12 @@ void TelemetryJson::Complete() {
SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
auto content = TopSection().dump();
// Send the telemetry async but don't handle the errors since they were written to the log
future = PostJson(endpoint_url, TopSection().dump(), true);
Common::DetachedTasks::AddTask(
[host{this->host}, username{this->username}, token{this->token}, content]() {
Client{host, username, token}.PostJson("/telemetry", content, true);
});
}
} // namespace WebService

View file

@ -5,7 +5,6 @@
#pragma once
#include <array>
#include <future>
#include <string>
#include "common/announce_multiplayer_room.h"
#include "common/telemetry.h"
@ -19,9 +18,8 @@ namespace WebService {
*/
class TelemetryJson : public Telemetry::VisitorInterface {
public:
TelemetryJson(const std::string& endpoint_url, const std::string& username,
const std::string& token)
: endpoint_url(endpoint_url), username(username), token(token) {}
TelemetryJson(const std::string& host, const std::string& username, const std::string& token)
: host(host), username(username), token(token) {}
~TelemetryJson() = default;
void Visit(const Telemetry::Field<bool>& field) override;
@ -53,10 +51,9 @@ private:
nlohmann::json output;
std::array<nlohmann::json, 7> sections;
std::string endpoint_url;
std::string host;
std::string username;
std::string token;
std::future<Common::WebResult> future;
};
} // namespace WebService

View file

@ -8,26 +8,20 @@
namespace WebService {
std::future<bool> VerifyLogin(std::string& username, std::string& token,
const std::string& endpoint_url, std::function<void()> func) {
auto get_func = [func, username](const std::string& reply) -> bool {
func();
bool VerifyLogin(const std::string& host, const std::string& username, const std::string& token) {
Client client(host, username, token);
auto reply = client.GetJson("/profile", false).returned_data;
if (reply.empty()) {
return false;
}
nlohmann::json json = nlohmann::json::parse(reply);
const auto iter = json.find("username");
if (reply.empty()) {
return false;
}
if (iter == json.end()) {
return username.empty();
}
nlohmann::json json = nlohmann::json::parse(reply);
const auto iter = json.find("username");
if (iter == json.end()) {
return username.empty();
}
return username == *iter;
};
UpdateCoreJWT(true, username, token);
return GetJson<bool>(get_func, endpoint_url, false);
return username == *iter;
}
} // namespace WebService

View file

@ -12,13 +12,11 @@ namespace WebService {
/**
* Checks if username and token is valid
* @param host the web API URL
* @param username Citra username to use for authentication.
* @param token Citra token to use for authentication.
* @param endpoint_url URL of the services.citra-emu.org endpoint.
* @param func A function that gets exectued when the verification is finished
* @returns Future with bool indicating whether the verification succeeded
* @returns a bool indicating whether the verification succeeded
*/
std::future<bool> VerifyLogin(std::string& username, std::string& token,
const std::string& endpoint_url, std::function<void()> func);
bool VerifyLogin(const std::string& host, const std::string& username, const std::string& token);
} // namespace WebService

View file

@ -20,334 +20,128 @@ constexpr int HTTPS_PORT = 443;
constexpr int TIMEOUT_SECONDS = 30;
std::string UpdateCoreJWT(bool force_new_token, const std::string& username,
const std::string& token) {
static std::string jwt;
if (jwt.empty() || force_new_token) {
if (!username.empty() && !token.empty()) {
std::future<Common::WebResult> future =
PostJson(Settings::values.web_api_url + "/jwt/internal", username, token);
jwt = future.get().returned_data;
}
}
return jwt;
}
Client::JWTCache Client::jwt_cache{};
std::unique_ptr<httplib::Client> GetClientFor(const LUrlParser::clParseURL& parsedUrl) {
namespace hl = httplib;
int port;
std::unique_ptr<hl::Client> cli;
if (parsedUrl.m_Scheme == "http") {
if (!parsedUrl.GetPort(&port)) {
port = HTTP_PORT;
}
return std::make_unique<hl::Client>(parsedUrl.m_Host.c_str(), port, TIMEOUT_SECONDS);
} else if (parsedUrl.m_Scheme == "https") {
if (!parsedUrl.GetPort(&port)) {
port = HTTPS_PORT;
}
return std::make_unique<hl::SSLClient>(parsedUrl.m_Host.c_str(), port, TIMEOUT_SECONDS);
} else {
LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
return nullptr;
Client::Client(const std::string& host, const std::string& username, const std::string& token)
: host(host), username(username), token(token) {
if (username == jwt_cache.username && token == jwt_cache.token) {
jwt = jwt_cache.jwt;
}
}
static Common::WebResult PostJsonAsyncFn(const std::string& url,
const LUrlParser::clParseURL& parsed_url,
const httplib::Headers& params, const std::string& data,
bool is_jwt_requested) {
static bool is_first_attempt = true;
namespace hl = httplib;
std::unique_ptr<hl::Client> cli = GetClientFor(parsed_url);
Common::WebResult Client::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) {
return Common::WebResult{Common::WebResult::Code::InvalidURL, "URL is invalid"};
auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
int port;
if (parsedUrl.m_Scheme == "http") {
if (!parsedUrl.GetPort(&port)) {
port = HTTP_PORT;
}
cli =
std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port, TIMEOUT_SECONDS);
} else if (parsedUrl.m_Scheme == "https") {
if (!parsedUrl.GetPort(&port)) {
port = HTTPS_PORT;
}
cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port,
TIMEOUT_SECONDS);
} else {
LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"};
}
}
if (cli == nullptr) {
LOG_ERROR(WebService, "Invalid URL {}", host + path);
return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"};
}
hl::Request request;
request.method = "POST";
request.path = "/" + parsed_url.m_Path;
httplib::Headers params;
if (!jwt.empty()) {
params = {
{std::string("Authorization"), fmt::format("Bearer {}", jwt)},
};
} else if (!username.empty()) {
params = {
{std::string("x-username"), username},
{std::string("x-token"), token},
};
}
params.emplace(std::string("api-version"), std::string(API_VERSION));
if (method != "GET") {
params.emplace(std::string("Content-Type"), std::string("application/json"));
};
httplib::Request request;
request.method = method;
request.path = path;
request.headers = params;
request.body = data;
hl::Response response;
httplib::Response response;
if (!cli->send(request, response)) {
LOG_ERROR(WebService, "POST to {} returned null", url);
LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
return Common::WebResult{Common::WebResult::Code::LibError, "Null response"};
}
if (response.status >= 400) {
LOG_ERROR(WebService, "POST to {} returned error status code: {}", url, response.status);
if (response.status == 401 && !is_jwt_requested && is_first_attempt) {
LOG_WARNING(WebService, "Requesting new JWT");
UpdateCoreJWT(true, Settings::values.citra_username, Settings::values.citra_token);
is_first_attempt = false;
PostJsonAsyncFn(url, parsed_url, params, data, is_jwt_requested);
is_first_attempt = true;
}
LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
response.status);
return Common::WebResult{Common::WebResult::Code::HttpError,
std::to_string(response.status)};
}
auto content_type = response.headers.find("content-type");
if (content_type == response.headers.end() ||
(content_type->second.find("application/json") == std::string::npos &&
content_type->second.find("text/html; charset=utf-8") == std::string::npos)) {
LOG_ERROR(WebService, "POST to {} returned wrong content: {}", url, content_type->second);
if (content_type == response.headers.end()) {
LOG_ERROR(WebService, "{} to {} returned no content", method, host + path);
return Common::WebResult{Common::WebResult::Code::WrongContent, ""};
}
if (content_type->second.find("application/json") == std::string::npos &&
content_type->second.find("text/html; charset=utf-8") == std::string::npos) {
LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
content_type->second);
return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"};
}
return Common::WebResult{Common::WebResult::Code::Success, "", response.body};
}
std::future<Common::WebResult> PostJson(const std::string& url, const std::string& data,
bool allow_anonymous) {
using lup = LUrlParser::clParseURL;
namespace hl = httplib;
lup parsedUrl = lup::ParseURL(url);
if (url.empty() || !parsedUrl.IsValid()) {
LOG_ERROR(WebService, "URL is invalid");
return std::async(std::launch::deferred, [] {
return Common::WebResult{Common::WebResult::Code::InvalidURL, "URL is invalid"};
});
void Client::UpdateJWT() {
if (!username.empty() && !token.empty()) {
auto result = GenericJson("POST", "/jwt/internal", "", "", username, token);
if (result.result_code != Common::WebResult::Code::Success) {
LOG_ERROR(WebService, "UpdateJWT failed");
} else {
jwt_cache.username = username;
jwt_cache.token = token;
jwt_cache.jwt = jwt = result.returned_data;
}
}
const std::string jwt =
UpdateCoreJWT(false, Settings::values.citra_username, Settings::values.citra_token);
const bool are_credentials_provided{!jwt.empty()};
if (!allow_anonymous && !are_credentials_provided) {
LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
return std::async(std::launch::deferred, [] {
return Common::WebResult{Common::WebResult::Code::CredentialsMissing,
"Credentials needed"};
});
}
// Built request header
hl::Headers params;
if (are_credentials_provided) {
// Authenticated request if credentials are provided
params = {{std::string("Authorization"), fmt::format("Bearer {}", jwt)},
{std::string("api-version"), std::string(API_VERSION)},
{std::string("Content-Type"), std::string("application/json")}};
} else {
// Otherwise, anonymous request
params = {{std::string("api-version"), std::string(API_VERSION)},
{std::string("Content-Type"), std::string("application/json")}};
}
// Post JSON asynchronously
return std::async(std::launch::async, PostJsonAsyncFn, url, parsedUrl, params, data, false);
}
std::future<Common::WebResult> PostJson(const std::string& url, const std::string& username,
const std::string& token) {
using lup = LUrlParser::clParseURL;
namespace hl = httplib;
lup parsedUrl = lup::ParseURL(url);
if (url.empty() || !parsedUrl.IsValid()) {
LOG_ERROR(WebService, "URL is invalid");
return std::async(std::launch::deferred, [] {
return Common::WebResult{Common::WebResult::Code::InvalidURL, ""};
});
Common::WebResult Client::GenericJson(const std::string& method, const std::string& path,
const std::string& data, bool allow_anonymous) {
if (jwt.empty()) {
UpdateJWT();
}
const bool are_credentials_provided{!token.empty() && !username.empty()};
if (!are_credentials_provided) {
if (jwt.empty() && !allow_anonymous) {
LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
return std::async(std::launch::deferred, [] {
return Common::WebResult{Common::WebResult::Code::CredentialsMissing, ""};
});
return Common::WebResult{Common::WebResult::Code::CredentialsMissing, "Credentials needed"};
}
// Built request header
hl::Headers params;
if (are_credentials_provided) {
// Authenticated request if credentials are provided
params = {{std::string("x-username"), username},
{std::string("x-token"), token},
{std::string("api-version"), std::string(API_VERSION)},
{std::string("Content-Type"), std::string("application/json")}};
} else {
// Otherwise, anonymous request
params = {{std::string("api-version"), std::string(API_VERSION)},
{std::string("Content-Type"), std::string("application/json")}};
auto result = GenericJson(method, path, data, jwt);
if (result.result_string == "401") {
// Try again with new JWT
UpdateJWT();
result = GenericJson(method, path, data, jwt);
}
// Post JSON asynchronously
return std::async(std::launch::async, PostJsonAsyncFn, url, parsedUrl, params, "", true);
}
template <typename T>
std::future<T> GetJson(std::function<T(const std::string&)> func, const std::string& url,
bool allow_anonymous) {
static bool is_first_attempt = true;
using lup = LUrlParser::clParseURL;
namespace hl = httplib;
lup parsedUrl = lup::ParseURL(url);
if (url.empty() || !parsedUrl.IsValid()) {
LOG_ERROR(WebService, "URL is invalid");
return std::async(std::launch::deferred, [func{std::move(func)}]() { return func(""); });
}
const std::string jwt =
UpdateCoreJWT(false, Settings::values.citra_username, Settings::values.citra_token);
const bool are_credentials_provided{!jwt.empty()};
if (!allow_anonymous && !are_credentials_provided) {
LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
return std::async(std::launch::deferred, [func{std::move(func)}]() { return func(""); });
}
// Built request header
hl::Headers params;
if (are_credentials_provided) {
params = {{std::string("Authorization"), fmt::format("Bearer {}", jwt)},
{std::string("api-version"), std::string(API_VERSION)}};
} else {
// Otherwise, anonymous request
params = {{std::string("api-version"), std::string(API_VERSION)}};
}
// Get JSON asynchronously
return std::async(std::launch::async, [func, url, parsedUrl, params, allow_anonymous] {
std::unique_ptr<hl::Client> cli = GetClientFor(parsedUrl);
if (cli == nullptr) {
return func("");
}
hl::Request request;
request.method = "GET";
request.path = "/" + parsedUrl.m_Path;
request.headers = params;
hl::Response response;
if (!cli->send(request, response)) {
LOG_ERROR(WebService, "GET to {} returned null", url);
return func("");
}
if (response.status >= 400) {
LOG_ERROR(WebService, "GET to {} returned error status code: {}", url, response.status);
if (response.status == 401 && is_first_attempt) {
LOG_WARNING(WebService, "Requesting new JWT");
UpdateCoreJWT(true, Settings::values.citra_username, Settings::values.citra_token);
is_first_attempt = false;
GetJson(func, url, allow_anonymous);
is_first_attempt = true;
}
return func("");
}
auto content_type = response.headers.find("content-type");
if (content_type == response.headers.end() ||
content_type->second.find("application/json") == std::string::npos) {
LOG_ERROR(WebService, "GET to {} returned wrong content: {}", url,
content_type->second);
return func("");
}
return func(response.body);
});
}
template std::future<bool> GetJson(std::function<bool(const std::string&)> func,
const std::string& url, bool allow_anonymous);
template std::future<AnnounceMultiplayerRoom::RoomList> GetJson(
std::function<AnnounceMultiplayerRoom::RoomList(const std::string&)> func,
const std::string& url, bool allow_anonymous);
void DeleteJson(const std::string& url, const std::string& data) {
static bool is_first_attempt = true;
using lup = LUrlParser::clParseURL;
namespace hl = httplib;
lup parsedUrl = lup::ParseURL(url);
if (url.empty() || !parsedUrl.IsValid()) {
LOG_ERROR(WebService, "URL is invalid");
return;
}
const std::string jwt =
UpdateCoreJWT(false, Settings::values.citra_username, Settings::values.citra_token);
const bool are_credentials_provided{!jwt.empty()};
if (!are_credentials_provided) {
LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
return;
}
// Built request header
hl::Headers params = {{std::string("Authorization"), fmt::format("Bearer {}", jwt)},
{std::string("api-version"), std::string(API_VERSION)},
{std::string("Content-Type"), std::string("application/json")}};
// Delete JSON asynchronously
std::async(std::launch::async, [url, parsedUrl, params, data] {
std::unique_ptr<hl::Client> cli = GetClientFor(parsedUrl);
if (cli == nullptr) {
return;
}
hl::Request request;
request.method = "DELETE";
request.path = "/" + parsedUrl.m_Path;
request.headers = params;
request.body = data;
hl::Response response;
if (!cli->send(request, response)) {
LOG_ERROR(WebService, "DELETE to {} returned null", url);
return;
}
if (response.status >= 400) {
LOG_ERROR(WebService, "DELETE to {} returned error status code: {}", url,
response.status);
if (response.status == 401 && is_first_attempt) {
LOG_WARNING(WebService, "Requesting new JWT");
UpdateCoreJWT(true, Settings::values.citra_username, Settings::values.citra_token);
is_first_attempt = false;
DeleteJson(url, data);
is_first_attempt = true;
}
return;
}
auto content_type = response.headers.find("content-type");
if (content_type == response.headers.end() ||
content_type->second.find("application/json") == std::string::npos) {
LOG_ERROR(WebService, "DELETE to {} returned wrong content: {}", url,
content_type->second);
return;
}
return;
});
return result;
}
} // namespace WebService

View file

@ -12,72 +12,80 @@
#include "common/announce_multiplayer_room.h"
#include "common/common_types.h"
namespace LUrlParser {
class clParseURL;
namespace httplib {
class Client;
}
namespace WebService {
/**
* Requests a new JWT if necessary
* @param force_new_token If true, force to request a new token from the server.
* @param username Citra username to use for authentication.
* @param token Citra token to use for authentication.
* @return string with the current JWT toke
*/
std::string UpdateCoreJWT(bool force_new_token, const std::string& username,
const std::string& token);
class Client {
public:
Client(const std::string& host, const std::string& username, const std::string& token);
/**
* Posts JSON to a api.citra-emu.org.
* @param url URL of the api.citra-emu.org endpoint to post data to.
* @param parsed_url Parsed URL used for the POST request.
* @param params Headers sent for the POST request.
* @param data String of JSON data to use for the body of the POST request.
* @param data If true, a JWT is requested in the function
* @return future with the returned value of the POST
*/
static Common::WebResult PostJsonAsyncFn(const std::string& url,
const LUrlParser::clParseURL& parsed_url,
const httplib::Headers& params, const std::string& data,
bool is_jwt_requested);
/**
* Posts JSON to the specified path.
* @param path the URL segment after the host address.
* @param data String of JSON data to use for the body of the POST request.
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
* @return the result of the request.
*/
Common::WebResult PostJson(const std::string& path, const std::string& data,
bool allow_anonymous) {
return GenericJson("POST", path, data, allow_anonymous);
}
/**
* Posts JSON to api.citra-emu.org.
* @param url URL of the api.citra-emu.org endpoint to post data to.
* @param data String of JSON data to use for the body of the POST request.
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
* @return future with the returned value of the POST
*/
std::future<Common::WebResult> PostJson(const std::string& url, const std::string& data,
bool allow_anonymous);
/**
* Gets JSON from the specified path.
* @param path the URL segment after the host address.
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
* @return the result of the request.
*/
Common::WebResult GetJson(const std::string& path, bool allow_anonymous) {
return GenericJson("GET", path, "", allow_anonymous);
}
/**
* Posts JSON to api.citra-emu.org.
* @param url URL of the api.citra-emu.org endpoint to post data to.
* @param username Citra username to use for authentication.
* @param token Citra token to use for authentication.
* @return future with the error or result of the POST
*/
std::future<Common::WebResult> PostJson(const std::string& url, const std::string& username,
const std::string& token);
/**
* Deletes JSON to the specified path.
* @param path the URL segment after the host address.
* @param data String of JSON data to use for the body of the DELETE request.
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
* @return the result of the request.
*/
Common::WebResult DeleteJson(const std::string& path, const std::string& data,
bool allow_anonymous) {
return GenericJson("DELETE", path, data, allow_anonymous);
}
/**
* Gets JSON from api.citra-emu.org.
* @param func A function that gets exectued when the json as a string is received
* @param url URL of the api.citra-emu.org endpoint to post data to.
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
* @return future that holds the return value T of the func
*/
template <typename T>
std::future<T> GetJson(std::function<T(const std::string&)> func, const std::string& url,
bool allow_anonymous);
private:
/// 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);
/**
* Delete JSON to api.citra-emu.org.
* @param url URL of the api.citra-emu.org endpoint to post data to.
* @param data String of JSON data to use for the body of the DELETE request.
*/
void DeleteJson(const std::string& url, const std::string& data);
/**
* 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::string username;
std::string token;
std::string jwt;
};
static JWTCache jwt_cache;
};
} // namespace WebService