diff options
| author | 2018-10-10 21:23:41 -0400 | |
|---|---|---|
| committer | 2018-10-10 22:29:35 -0400 | |
| commit | 183a664405661fb531b82e2a0a3a8c6651a1ce8a (patch) | |
| tree | 6fab20b1cfbb116f5031de88efb809a541cbbcff /src/web_service | |
| parent | telemetry_json: Use the PImpl idiom to avoid unnecessary dependency exposure (diff) | |
| download | yuzu-183a664405661fb531b82e2a0a3a8c6651a1ce8a.tar.gz yuzu-183a664405661fb531b82e2a0a3a8c6651a1ce8a.tar.xz yuzu-183a664405661fb531b82e2a0a3a8c6651a1ce8a.zip | |
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.
Diffstat (limited to 'src/web_service')
| -rw-r--r-- | src/web_service/telemetry_json.cpp | 1 | ||||
| -rw-r--r-- | src/web_service/verify_login.cpp | 1 | ||||
| -rw-r--r-- | src/web_service/web_backend.cpp | 235 | ||||
| -rw-r--r-- | src/web_service/web_backend.h | 58 |
4 files changed, 153 insertions, 142 deletions
diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp index 3c5590054..0a8f2bd9e 100644 --- a/src/web_service/telemetry_json.cpp +++ b/src/web_service/telemetry_json.cpp | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #include <json.hpp> | 5 | #include <json.hpp> |
| 6 | #include "common/detached_tasks.h" | 6 | #include "common/detached_tasks.h" |
| 7 | #include "common/web_result.h" | ||
| 7 | #include "web_service/telemetry_json.h" | 8 | #include "web_service/telemetry_json.h" |
| 8 | #include "web_service/web_backend.h" | 9 | #include "web_service/web_backend.h" |
| 9 | 10 | ||
diff --git a/src/web_service/verify_login.cpp b/src/web_service/verify_login.cpp index 124aa3863..ca4b43b93 100644 --- a/src/web_service/verify_login.cpp +++ b/src/web_service/verify_login.cpp | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <json.hpp> | 5 | #include <json.hpp> |
| 6 | #include "common/web_result.h" | ||
| 6 | #include "web_service/verify_login.h" | 7 | #include "web_service/verify_login.h" |
| 7 | #include "web_service/web_backend.h" | 8 | #include "web_service/web_backend.h" |
| 8 | 9 | ||
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp index 787b0fbcb..b7737b615 100644 --- a/src/web_service/web_backend.cpp +++ b/src/web_service/web_backend.cpp | |||
| @@ -3,9 +3,11 @@ | |||
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <cstdlib> | 5 | #include <cstdlib> |
| 6 | #include <mutex> | ||
| 6 | #include <string> | 7 | #include <string> |
| 7 | #include <thread> | ||
| 8 | #include <LUrlParser.h> | 8 | #include <LUrlParser.h> |
| 9 | #include <httplib.h> | ||
| 10 | #include "common/common_types.h" | ||
| 9 | #include "common/logging/log.h" | 11 | #include "common/logging/log.h" |
| 10 | #include "common/web_result.h" | 12 | #include "common/web_result.h" |
| 11 | #include "core/settings.h" | 13 | #include "core/settings.h" |
| @@ -20,99 +22,132 @@ constexpr u32 HTTPS_PORT = 443; | |||
| 20 | 22 | ||
| 21 | constexpr u32 TIMEOUT_SECONDS = 30; | 23 | constexpr u32 TIMEOUT_SECONDS = 30; |
| 22 | 24 | ||
| 23 | Client::JWTCache Client::jwt_cache{}; | 25 | struct Client::Impl { |
| 26 | Impl(std::string host, std::string username, std::string token) | ||
| 27 | : host{std::move(host)}, username{std::move(username)}, token{std::move(token)} { | ||
| 28 | std::lock_guard<std::mutex> lock(jwt_cache.mutex); | ||
| 29 | if (this->username == jwt_cache.username && this->token == jwt_cache.token) { | ||
| 30 | jwt = jwt_cache.jwt; | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | /// A generic function handles POST, GET and DELETE request together | ||
| 35 | Common::WebResult GenericJson(const std::string& method, const std::string& path, | ||
| 36 | const std::string& data, bool allow_anonymous) { | ||
| 37 | if (jwt.empty()) { | ||
| 38 | UpdateJWT(); | ||
| 39 | } | ||
| 40 | |||
| 41 | if (jwt.empty() && !allow_anonymous) { | ||
| 42 | LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); | ||
| 43 | return Common::WebResult{Common::WebResult::Code::CredentialsMissing, | ||
| 44 | "Credentials needed"}; | ||
| 45 | } | ||
| 46 | |||
| 47 | auto result = GenericJson(method, path, data, jwt); | ||
| 48 | if (result.result_string == "401") { | ||
| 49 | // Try again with new JWT | ||
| 50 | UpdateJWT(); | ||
| 51 | result = GenericJson(method, path, data, jwt); | ||
| 52 | } | ||
| 24 | 53 | ||
| 25 | Client::Client(const std::string& host, const std::string& username, const std::string& token) | 54 | return result; |
| 26 | : host(host), username(username), token(token) { | ||
| 27 | std::lock_guard<std::mutex> lock(jwt_cache.mutex); | ||
| 28 | if (username == jwt_cache.username && token == jwt_cache.token) { | ||
| 29 | jwt = jwt_cache.jwt; | ||
| 30 | } | 55 | } |
| 31 | } | ||
| 32 | 56 | ||
| 33 | Common::WebResult Client::GenericJson(const std::string& method, const std::string& path, | 57 | /** |
| 34 | const std::string& data, const std::string& jwt, | 58 | * A generic function with explicit authentication method specified |
| 35 | const std::string& username, const std::string& token) { | 59 | * JWT is used if the jwt parameter is not empty |
| 36 | if (cli == nullptr) { | 60 | * username + token is used if jwt is empty but username and token are not empty |
| 37 | auto parsedUrl = LUrlParser::clParseURL::ParseURL(host); | 61 | * anonymous if all of jwt, username and token are empty |
| 38 | int port; | 62 | */ |
| 39 | if (parsedUrl.m_Scheme == "http") { | 63 | Common::WebResult GenericJson(const std::string& method, const std::string& path, |
| 40 | if (!parsedUrl.GetPort(&port)) { | 64 | const std::string& data, const std::string& jwt = "", |
| 41 | port = HTTP_PORT; | 65 | const std::string& username = "", const std::string& token = "") { |
| 42 | } | 66 | if (cli == nullptr) { |
| 43 | cli = | 67 | auto parsedUrl = LUrlParser::clParseURL::ParseURL(host); |
| 44 | std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port, TIMEOUT_SECONDS); | 68 | int port; |
| 45 | } else if (parsedUrl.m_Scheme == "https") { | 69 | if (parsedUrl.m_Scheme == "http") { |
| 46 | if (!parsedUrl.GetPort(&port)) { | 70 | if (!parsedUrl.GetPort(&port)) { |
| 47 | port = HTTPS_PORT; | 71 | port = HTTP_PORT; |
| 72 | } | ||
| 73 | cli = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port, | ||
| 74 | TIMEOUT_SECONDS); | ||
| 75 | } else if (parsedUrl.m_Scheme == "https") { | ||
| 76 | if (!parsedUrl.GetPort(&port)) { | ||
| 77 | port = HTTPS_PORT; | ||
| 78 | } | ||
| 79 | cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port, | ||
| 80 | TIMEOUT_SECONDS); | ||
| 81 | } else { | ||
| 82 | LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme); | ||
| 83 | return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"}; | ||
| 48 | } | 84 | } |
| 49 | cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port, | ||
| 50 | TIMEOUT_SECONDS); | ||
| 51 | } else { | ||
| 52 | LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme); | ||
| 53 | return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"}; | ||
| 54 | } | 85 | } |
| 55 | } | 86 | if (cli == nullptr) { |
| 56 | if (cli == nullptr) { | 87 | LOG_ERROR(WebService, "Invalid URL {}", host + path); |
| 57 | LOG_ERROR(WebService, "Invalid URL {}", host + path); | 88 | return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"}; |
| 58 | return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"}; | 89 | } |
| 59 | } | ||
| 60 | 90 | ||
| 61 | httplib::Headers params; | 91 | httplib::Headers params; |
| 62 | if (!jwt.empty()) { | 92 | if (!jwt.empty()) { |
| 63 | params = { | 93 | params = { |
| 64 | {std::string("Authorization"), fmt::format("Bearer {}", jwt)}, | 94 | {std::string("Authorization"), fmt::format("Bearer {}", jwt)}, |
| 65 | }; | 95 | }; |
| 66 | } else if (!username.empty()) { | 96 | } else if (!username.empty()) { |
| 67 | params = { | 97 | params = { |
| 68 | {std::string("x-username"), username}, | 98 | {std::string("x-username"), username}, |
| 69 | {std::string("x-token"), token}, | 99 | {std::string("x-token"), token}, |
| 100 | }; | ||
| 101 | } | ||
| 102 | |||
| 103 | params.emplace(std::string("api-version"), | ||
| 104 | std::string(API_VERSION.begin(), API_VERSION.end())); | ||
| 105 | if (method != "GET") { | ||
| 106 | params.emplace(std::string("Content-Type"), std::string("application/json")); | ||
| 70 | }; | 107 | }; |
| 71 | } | ||
| 72 | 108 | ||
| 73 | params.emplace(std::string("api-version"), std::string(API_VERSION.begin(), API_VERSION.end())); | 109 | httplib::Request request; |
| 74 | if (method != "GET") { | 110 | request.method = method; |
| 75 | params.emplace(std::string("Content-Type"), std::string("application/json")); | 111 | request.path = path; |
| 76 | }; | 112 | request.headers = params; |
| 113 | request.body = data; | ||
| 77 | 114 | ||
| 78 | httplib::Request request; | 115 | httplib::Response response; |
| 79 | request.method = method; | ||
| 80 | request.path = path; | ||
| 81 | request.headers = params; | ||
| 82 | request.body = data; | ||
| 83 | 116 | ||
| 84 | httplib::Response response; | 117 | if (!cli->send(request, response)) { |
| 118 | LOG_ERROR(WebService, "{} to {} returned null", method, host + path); | ||
| 119 | return Common::WebResult{Common::WebResult::Code::LibError, "Null response"}; | ||
| 120 | } | ||
| 85 | 121 | ||
| 86 | if (!cli->send(request, response)) { | 122 | if (response.status >= 400) { |
| 87 | LOG_ERROR(WebService, "{} to {} returned null", method, host + path); | 123 | LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path, |
| 88 | return Common::WebResult{Common::WebResult::Code::LibError, "Null response"}; | 124 | response.status); |
| 89 | } | 125 | return Common::WebResult{Common::WebResult::Code::HttpError, |
| 126 | std::to_string(response.status)}; | ||
| 127 | } | ||
| 90 | 128 | ||
| 91 | if (response.status >= 400) { | 129 | auto content_type = response.headers.find("content-type"); |
| 92 | LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path, | ||
| 93 | response.status); | ||
| 94 | return Common::WebResult{Common::WebResult::Code::HttpError, | ||
| 95 | std::to_string(response.status)}; | ||
| 96 | } | ||
| 97 | 130 | ||
| 98 | auto content_type = response.headers.find("content-type"); | 131 | if (content_type == response.headers.end()) { |
| 132 | LOG_ERROR(WebService, "{} to {} returned no content", method, host + path); | ||
| 133 | return Common::WebResult{Common::WebResult::Code::WrongContent, ""}; | ||
| 134 | } | ||
| 99 | 135 | ||
| 100 | if (content_type == response.headers.end()) { | 136 | if (content_type->second.find("application/json") == std::string::npos && |
| 101 | LOG_ERROR(WebService, "{} to {} returned no content", method, host + path); | 137 | content_type->second.find("text/html; charset=utf-8") == std::string::npos) { |
| 102 | return Common::WebResult{Common::WebResult::Code::WrongContent, ""}; | 138 | LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path, |
| 139 | content_type->second); | ||
| 140 | return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"}; | ||
| 141 | } | ||
| 142 | return Common::WebResult{Common::WebResult::Code::Success, "", response.body}; | ||
| 103 | } | 143 | } |
| 104 | 144 | ||
| 105 | if (content_type->second.find("application/json") == std::string::npos && | 145 | // Retrieve a new JWT from given username and token |
| 106 | content_type->second.find("text/html; charset=utf-8") == std::string::npos) { | 146 | void UpdateJWT() { |
| 107 | LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path, | 147 | if (username.empty() || token.empty()) { |
| 108 | content_type->second); | 148 | return; |
| 109 | return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"}; | 149 | } |
| 110 | } | ||
| 111 | return Common::WebResult{Common::WebResult::Code::Success, "", response.body}; | ||
| 112 | } | ||
| 113 | 150 | ||
| 114 | void Client::UpdateJWT() { | ||
| 115 | if (!username.empty() && !token.empty()) { | ||
| 116 | auto result = GenericJson("POST", "/jwt/internal", "", "", username, token); | 151 | auto result = GenericJson("POST", "/jwt/internal", "", "", username, token); |
| 117 | if (result.result_code != Common::WebResult::Code::Success) { | 152 | if (result.result_code != Common::WebResult::Code::Success) { |
| 118 | LOG_ERROR(WebService, "UpdateJWT failed"); | 153 | LOG_ERROR(WebService, "UpdateJWT failed"); |
| @@ -123,27 +158,39 @@ void Client::UpdateJWT() { | |||
| 123 | jwt_cache.jwt = jwt = result.returned_data; | 158 | jwt_cache.jwt = jwt = result.returned_data; |
| 124 | } | 159 | } |
| 125 | } | 160 | } |
| 126 | } | ||
| 127 | 161 | ||
| 128 | Common::WebResult Client::GenericJson(const std::string& method, const std::string& path, | 162 | std::string host; |
| 129 | const std::string& data, bool allow_anonymous) { | 163 | std::string username; |
| 130 | if (jwt.empty()) { | 164 | std::string token; |
| 131 | UpdateJWT(); | 165 | std::string jwt; |
| 132 | } | 166 | std::unique_ptr<httplib::Client> cli; |
| 167 | |||
| 168 | struct JWTCache { | ||
| 169 | std::mutex mutex; | ||
| 170 | std::string username; | ||
| 171 | std::string token; | ||
| 172 | std::string jwt; | ||
| 173 | }; | ||
| 174 | static inline JWTCache jwt_cache; | ||
| 175 | }; | ||
| 133 | 176 | ||
| 134 | if (jwt.empty() && !allow_anonymous) { | 177 | Client::Client(std::string host, std::string username, std::string token) |
| 135 | LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); | 178 | : impl{std::make_unique<Impl>(std::move(host), std::move(username), std::move(token))} {} |
| 136 | return Common::WebResult{Common::WebResult::Code::CredentialsMissing, "Credentials needed"}; | ||
| 137 | } | ||
| 138 | 179 | ||
| 139 | auto result = GenericJson(method, path, data, jwt); | 180 | Client::~Client() = default; |
| 140 | if (result.result_string == "401") { | 181 | |
| 141 | // Try again with new JWT | 182 | Common::WebResult Client::PostJson(const std::string& path, const std::string& data, |
| 142 | UpdateJWT(); | 183 | bool allow_anonymous) { |
| 143 | result = GenericJson(method, path, data, jwt); | 184 | return impl->GenericJson("POST", path, data, allow_anonymous); |
| 144 | } | 185 | } |
| 186 | |||
| 187 | Common::WebResult Client::GetJson(const std::string& path, bool allow_anonymous) { | ||
| 188 | return impl->GenericJson("GET", path, "", allow_anonymous); | ||
| 189 | } | ||
| 145 | 190 | ||
| 146 | return result; | 191 | Common::WebResult Client::DeleteJson(const std::string& path, const std::string& data, |
| 192 | bool allow_anonymous) { | ||
| 193 | return impl->GenericJson("DELETE", path, data, allow_anonymous); | ||
| 147 | } | 194 | } |
| 148 | 195 | ||
| 149 | } // namespace WebService | 196 | } // namespace WebService |
diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h index d75fbcc15..c637e09df 100644 --- a/src/web_service/web_backend.h +++ b/src/web_service/web_backend.h | |||
| @@ -4,23 +4,19 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <functional> | 7 | #include <memory> |
| 8 | #include <mutex> | ||
| 9 | #include <string> | 8 | #include <string> |
| 10 | #include <tuple> | ||
| 11 | #include <httplib.h> | ||
| 12 | #include "common/common_types.h" | ||
| 13 | #include "common/web_result.h" | ||
| 14 | 9 | ||
| 15 | namespace httplib { | 10 | namespace Common { |
| 16 | class Client; | 11 | struct WebResult; |
| 17 | } | 12 | } |
| 18 | 13 | ||
| 19 | namespace WebService { | 14 | namespace WebService { |
| 20 | 15 | ||
| 21 | class Client { | 16 | class Client { |
| 22 | public: | 17 | public: |
| 23 | Client(const std::string& host, const std::string& username, const std::string& token); | 18 | Client(std::string host, std::string username, std::string token); |
| 19 | ~Client(); | ||
| 24 | 20 | ||
| 25 | /** | 21 | /** |
| 26 | * Posts JSON to the specified path. | 22 | * Posts JSON to the specified path. |
| @@ -30,9 +26,7 @@ public: | |||
| 30 | * @return the result of the request. | 26 | * @return the result of the request. |
| 31 | */ | 27 | */ |
| 32 | Common::WebResult PostJson(const std::string& path, const std::string& data, | 28 | Common::WebResult PostJson(const std::string& path, const std::string& data, |
| 33 | bool allow_anonymous) { | 29 | bool allow_anonymous); |
| 34 | return GenericJson("POST", path, data, allow_anonymous); | ||
| 35 | } | ||
| 36 | 30 | ||
| 37 | /** | 31 | /** |
| 38 | * Gets JSON from the specified path. | 32 | * Gets JSON from the specified path. |
| @@ -40,9 +34,7 @@ public: | |||
| 40 | * @param allow_anonymous If true, allow anonymous unauthenticated requests. | 34 | * @param allow_anonymous If true, allow anonymous unauthenticated requests. |
| 41 | * @return the result of the request. | 35 | * @return the result of the request. |
| 42 | */ | 36 | */ |
| 43 | Common::WebResult GetJson(const std::string& path, bool allow_anonymous) { | 37 | Common::WebResult GetJson(const std::string& path, bool allow_anonymous); |
| 44 | return GenericJson("GET", path, "", allow_anonymous); | ||
| 45 | } | ||
| 46 | 38 | ||
| 47 | /** | 39 | /** |
| 48 | * Deletes JSON to the specified path. | 40 | * Deletes JSON to the specified path. |
| @@ -52,41 +44,11 @@ public: | |||
| 52 | * @return the result of the request. | 44 | * @return the result of the request. |
| 53 | */ | 45 | */ |
| 54 | Common::WebResult DeleteJson(const std::string& path, const std::string& data, | 46 | Common::WebResult DeleteJson(const std::string& path, const std::string& data, |
| 55 | bool allow_anonymous) { | 47 | bool allow_anonymous); |
| 56 | return GenericJson("DELETE", path, data, allow_anonymous); | ||
| 57 | } | ||
| 58 | 48 | ||
| 59 | private: | 49 | private: |
| 60 | /// A generic function handles POST, GET and DELETE request together | 50 | struct Impl; |
| 61 | Common::WebResult GenericJson(const std::string& method, const std::string& path, | 51 | std::unique_ptr<Impl> impl; |
| 62 | const std::string& data, bool allow_anonymous); | ||
| 63 | |||
| 64 | /** | ||
| 65 | * A generic function with explicit authentication method specified | ||
| 66 | * JWT is used if the jwt parameter is not empty | ||
| 67 | * username + token is used if jwt is empty but username and token are not empty | ||
| 68 | * anonymous if all of jwt, username and token are empty | ||
| 69 | */ | ||
| 70 | Common::WebResult GenericJson(const std::string& method, const std::string& path, | ||
| 71 | const std::string& data, const std::string& jwt = "", | ||
| 72 | const std::string& username = "", const std::string& token = ""); | ||
| 73 | |||
| 74 | // Retrieve a new JWT from given username and token | ||
| 75 | void UpdateJWT(); | ||
| 76 | |||
| 77 | std::string host; | ||
| 78 | std::string username; | ||
| 79 | std::string token; | ||
| 80 | std::string jwt; | ||
| 81 | std::unique_ptr<httplib::Client> cli; | ||
| 82 | |||
| 83 | struct JWTCache { | ||
| 84 | std::mutex mutex; | ||
| 85 | std::string username; | ||
| 86 | std::string token; | ||
| 87 | std::string jwt; | ||
| 88 | }; | ||
| 89 | static JWTCache jwt_cache; | ||
| 90 | }; | 52 | }; |
| 91 | 53 | ||
| 92 | } // namespace WebService | 54 | } // namespace WebService |