Sfoglia il codice sorgente

Merge pull request #1473 from lioncash/cmake

web_service: Make linkage of web_service-related externals and the library private
bunnei 7 anni fa
parent
commit
548958bcaf

+ 1 - 0
src/common/web_result.h

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

+ 2 - 2
src/core/CMakeLists.txt

@@ -400,8 +400,8 @@ create_target_directory_groups(core)
 target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
 target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn open_source_archives)
 if (ENABLE_WEB_SERVICE)
-    add_definitions(-DENABLE_WEB_SERVICE)
-    target_link_libraries(core PUBLIC json-headers web_service)
+    target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
+    target_link_libraries(core PRIVATE web_service)
 endif()
 
 if (ARCHITECTURE_x86_64)

+ 54 - 36
src/web_service/telemetry_json.cpp

@@ -2,96 +2,114 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
-#include <thread>
-#include "common/assert.h"
+#include <json.hpp>
 #include "common/detached_tasks.h"
+#include "common/web_result.h"
 #include "web_service/telemetry_json.h"
 #include "web_service/web_backend.h"
 
 namespace WebService {
 
-TelemetryJson::TelemetryJson(const std::string& host, const std::string& username,
-                             const std::string& token)
-    : host(std::move(host)), username(std::move(username)), token(std::move(token)) {}
-TelemetryJson::~TelemetryJson() = default;
+struct TelemetryJson::Impl {
+    Impl(std::string host, std::string username, std::string token)
+        : host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {}
 
-template <class T>
-void TelemetryJson::Serialize(Telemetry::FieldType type, const std::string& name, T value) {
-    sections[static_cast<u8>(type)][name] = value;
-}
+    nlohmann::json& TopSection() {
+        return sections[static_cast<u8>(Telemetry::FieldType::None)];
+    }
 
-void TelemetryJson::SerializeSection(Telemetry::FieldType type, const std::string& name) {
-    TopSection()[name] = sections[static_cast<unsigned>(type)];
-}
+    const nlohmann::json& TopSection() const {
+        return sections[static_cast<u8>(Telemetry::FieldType::None)];
+    }
+
+    template <class T>
+    void Serialize(Telemetry::FieldType type, const std::string& name, T value) {
+        sections[static_cast<u8>(type)][name] = value;
+    }
+
+    void SerializeSection(Telemetry::FieldType type, const std::string& name) {
+        TopSection()[name] = sections[static_cast<unsigned>(type)];
+    }
+
+    nlohmann::json output;
+    std::array<nlohmann::json, 7> sections;
+    std::string host;
+    std::string username;
+    std::string token;
+};
+
+TelemetryJson::TelemetryJson(std::string host, std::string username, std::string token)
+    : impl{std::make_unique<Impl>(std::move(host), std::move(username), std::move(token))} {}
+TelemetryJson::~TelemetryJson() = default;
 
 void TelemetryJson::Visit(const Telemetry::Field<bool>& field) {
-    Serialize(field.GetType(), field.GetName(), field.GetValue());
+    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 }
 
 void TelemetryJson::Visit(const Telemetry::Field<double>& field) {
-    Serialize(field.GetType(), field.GetName(), field.GetValue());
+    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 }
 
 void TelemetryJson::Visit(const Telemetry::Field<float>& field) {
-    Serialize(field.GetType(), field.GetName(), field.GetValue());
+    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 }
 
 void TelemetryJson::Visit(const Telemetry::Field<u8>& field) {
-    Serialize(field.GetType(), field.GetName(), field.GetValue());
+    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 }
 
 void TelemetryJson::Visit(const Telemetry::Field<u16>& field) {
-    Serialize(field.GetType(), field.GetName(), field.GetValue());
+    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 }
 
 void TelemetryJson::Visit(const Telemetry::Field<u32>& field) {
-    Serialize(field.GetType(), field.GetName(), field.GetValue());
+    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 }
 
 void TelemetryJson::Visit(const Telemetry::Field<u64>& field) {
-    Serialize(field.GetType(), field.GetName(), field.GetValue());
+    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 }
 
 void TelemetryJson::Visit(const Telemetry::Field<s8>& field) {
-    Serialize(field.GetType(), field.GetName(), field.GetValue());
+    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 }
 
 void TelemetryJson::Visit(const Telemetry::Field<s16>& field) {
-    Serialize(field.GetType(), field.GetName(), field.GetValue());
+    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 }
 
 void TelemetryJson::Visit(const Telemetry::Field<s32>& field) {
-    Serialize(field.GetType(), field.GetName(), field.GetValue());
+    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 }
 
 void TelemetryJson::Visit(const Telemetry::Field<s64>& field) {
-    Serialize(field.GetType(), field.GetName(), field.GetValue());
+    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 }
 
 void TelemetryJson::Visit(const Telemetry::Field<std::string>& field) {
-    Serialize(field.GetType(), field.GetName(), field.GetValue());
+    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 }
 
 void TelemetryJson::Visit(const Telemetry::Field<const char*>& field) {
-    Serialize(field.GetType(), field.GetName(), std::string(field.GetValue()));
+    impl->Serialize(field.GetType(), field.GetName(), std::string(field.GetValue()));
 }
 
 void TelemetryJson::Visit(const Telemetry::Field<std::chrono::microseconds>& field) {
-    Serialize(field.GetType(), field.GetName(), field.GetValue().count());
+    impl->Serialize(field.GetType(), field.GetName(), field.GetValue().count());
 }
 
 void TelemetryJson::Complete() {
-    SerializeSection(Telemetry::FieldType::App, "App");
-    SerializeSection(Telemetry::FieldType::Session, "Session");
-    SerializeSection(Telemetry::FieldType::Performance, "Performance");
-    SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
-    SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
-    SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
-
-    auto content = TopSection().dump();
+    impl->SerializeSection(Telemetry::FieldType::App, "App");
+    impl->SerializeSection(Telemetry::FieldType::Session, "Session");
+    impl->SerializeSection(Telemetry::FieldType::Performance, "Performance");
+    impl->SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
+    impl->SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
+    impl->SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
+
+    auto content = impl->TopSection().dump();
     // Send the telemetry async but don't handle the errors since they were written to the log
     Common::DetachedTasks::AddTask(
-        [host{this->host}, username{this->username}, token{this->token}, content]() {
+        [host{impl->host}, username{impl->username}, token{impl->token}, content]() {
             Client{host, username, token}.PostJson("/telemetry", content, true);
         });
 }

+ 5 - 19
src/web_service/telemetry_json.h

@@ -4,11 +4,9 @@
 
 #pragma once
 
-#include <array>
+#include <chrono>
 #include <string>
-#include <json.hpp>
 #include "common/telemetry.h"
-#include "common/web_result.h"
 
 namespace WebService {
 
@@ -18,8 +16,8 @@ namespace WebService {
  */
 class TelemetryJson : public Telemetry::VisitorInterface {
 public:
-    TelemetryJson(const std::string& host, const std::string& username, const std::string& token);
-    ~TelemetryJson();
+    TelemetryJson(std::string host, std::string username, std::string token);
+    ~TelemetryJson() override;
 
     void Visit(const Telemetry::Field<bool>& field) override;
     void Visit(const Telemetry::Field<double>& field) override;
@@ -39,20 +37,8 @@ public:
     void Complete() override;
 
 private:
-    nlohmann::json& TopSection() {
-        return sections[static_cast<u8>(Telemetry::FieldType::None)];
-    }
-
-    template <class T>
-    void Serialize(Telemetry::FieldType type, const std::string& name, T value);
-
-    void SerializeSection(Telemetry::FieldType type, const std::string& name);
-
-    nlohmann::json output;
-    std::array<nlohmann::json, 7> sections;
-    std::string host;
-    std::string username;
-    std::string token;
+    struct Impl;
+    std::unique_ptr<Impl> impl;
 };
 
 } // namespace WebService

+ 1 - 0
src/web_service/verify_login.cpp

@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <json.hpp>
+#include "common/web_result.h"
 #include "web_service/verify_login.h"
 #include "web_service/web_backend.h"
 

+ 141 - 94
src/web_service/web_backend.cpp

@@ -3,9 +3,11 @@
 // Refer to the license.txt file included.
 
 #include <cstdlib>
+#include <mutex>
 #include <string>
-#include <thread>
 #include <LUrlParser.h>
+#include <httplib.h>
+#include "common/common_types.h"
 #include "common/logging/log.h"
 #include "common/web_result.h"
 #include "core/settings.h"
@@ -20,99 +22,132 @@ constexpr u32 HTTPS_PORT = 443;
 
 constexpr u32 TIMEOUT_SECONDS = 30;
 
-Client::JWTCache Client::jwt_cache{};
+struct Client::Impl {
+    Impl(std::string host, std::string username, std::string token)
+        : host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {
+        std::lock_guard<std::mutex> lock(jwt_cache.mutex);
+        if (this->username == jwt_cache.username && this->token == jwt_cache.token) {
+            jwt = jwt_cache.jwt;
+        }
+    }
+
+    /// 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();
+        }
+
+        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);
+        }
 
-Client::Client(const std::string& host, const std::string& username, const std::string& token)
-    : host(host), username(username), token(token) {
-    std::lock_guard<std::mutex> lock(jwt_cache.mutex);
-    if (username == jwt_cache.username && token == jwt_cache.token) {
-        jwt = jwt_cache.jwt;
+        return result;
     }
-}
 
-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) {
-        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;
+    /**
+     * 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) {
+            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"};
             }
-            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"};
-    }
+        if (cli == nullptr) {
+            LOG_ERROR(WebService, "Invalid URL {}", host + path);
+            return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"};
+        }
 
-    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},
+        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.begin(), API_VERSION.end()));
+        if (method != "GET") {
+            params.emplace(std::string("Content-Type"), std::string("application/json"));
         };
-    }
 
-    params.emplace(std::string("api-version"), std::string(API_VERSION.begin(), API_VERSION.end()));
-    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;
 
-    httplib::Request request;
-    request.method = method;
-    request.path = path;
-    request.headers = params;
-    request.body = data;
+        httplib::Response response;
 
-    httplib::Response response;
+        if (!cli->send(request, response)) {
+            LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
+            return Common::WebResult{Common::WebResult::Code::LibError, "Null response"};
+        }
 
-    if (!cli->send(request, response)) {
-        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, "{} to {} returned error status code: {}", method, host + path,
+                      response.status);
+            return Common::WebResult{Common::WebResult::Code::HttpError,
+                                     std::to_string(response.status)};
+        }
 
-    if (response.status >= 400) {
-        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");
 
-    auto content_type = response.headers.find("content-type");
+        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 == 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};
     }
 
-    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};
-}
+    // 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);
         if (result.result_code != Common::WebResult::Code::Success) {
             LOG_ERROR(WebService, "UpdateJWT failed");
@@ -123,27 +158,39 @@ void Client::UpdateJWT() {
             jwt_cache.jwt = jwt = result.returned_data;
         }
     }
-}
 
-Common::WebResult Client::GenericJson(const std::string& method, const std::string& path,
-                                      const std::string& data, bool allow_anonymous) {
-    if (jwt.empty()) {
-        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 inline JWTCache jwt_cache;
+};
 
-    if (jwt.empty() && !allow_anonymous) {
-        LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
-        return Common::WebResult{Common::WebResult::Code::CredentialsMissing, "Credentials needed"};
-    }
+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))} {}
 
-    auto result = GenericJson(method, path, data, jwt);
-    if (result.result_string == "401") {
-        // Try again with new JWT
-        UpdateJWT();
-        result = GenericJson(method, path, data, jwt);
-    }
+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::GetJson(const std::string& path, bool allow_anonymous) {
+    return impl->GenericJson("GET", path, "", allow_anonymous);
+}
 
-    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

+ 10 - 48
src/web_service/web_backend.h

@@ -4,23 +4,19 @@
 
 #pragma once
 
-#include <functional>
-#include <mutex>
+#include <memory>
 #include <string>
-#include <tuple>
-#include <httplib.h>
-#include "common/common_types.h"
-#include "common/web_result.h"
 
-namespace httplib {
-class Client;
+namespace Common {
+struct WebResult;
 }
 
 namespace WebService {
 
 class Client {
 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.
@@ -30,9 +26,7 @@ public:
      * @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);
-    }
+                               bool allow_anonymous);
 
     /**
      * Gets JSON from the specified path.
@@ -40,9 +34,7 @@ public:
      * @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);
-    }
+    Common::WebResult GetJson(const std::string& path, bool allow_anonymous);
 
     /**
      * Deletes JSON to the specified path.
@@ -52,41 +44,11 @@ public:
      * @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);
-    }
+                                 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);
-
-    /**
-     * 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;
+    struct Impl;
+    std::unique_ptr<Impl> impl;
 };
 
 } // namespace WebService