mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	Move WebServices to use LibreSSL + cpp-httplib (#3501)
Move WebServices to use LibreSSL + cpp-httplib Remove curl + openssl build dependencies
This commit is contained in:
		
							parent
							
								
									e2c5666883
								
							
						
					
					
						commit
						9283053701
					
				
					 18 changed files with 2633 additions and 150 deletions
				
			
		|  | @ -11,4 +11,8 @@ add_library(web_service STATIC | |||
| 
 | ||||
| create_target_directory_groups(web_service) | ||||
| 
 | ||||
| target_link_libraries(web_service PUBLIC common cpr json-headers) | ||||
| get_directory_property(OPENSSL_LIBS | ||||
|         DIRECTORY ${CMAKE_SOURCE_DIR}/externals/libressl | ||||
|         DEFINITION OPENSSL_LIBS) | ||||
| add_definitions(-DCPPHTTPLIB_OPENSSL_SUPPORT) | ||||
| target_link_libraries(web_service PUBLIC common json-headers ${OPENSSL_LIBS} httplib lurlparser) | ||||
|  |  | |||
|  | @ -2,13 +2,11 @@ | |||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
| #include <winsock.h> | ||||
| #endif | ||||
| 
 | ||||
| #include <cstdlib> | ||||
| #include <string> | ||||
| #include <thread> | ||||
| #include <cpr/cpr.h> | ||||
| #include <LUrlParser.h> | ||||
| #include <httplib.h> | ||||
| #include "common/announce_multiplayer_room.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "web_service/web_backend.h" | ||||
|  | @ -17,25 +15,45 @@ namespace WebService { | |||
| 
 | ||||
| static constexpr char API_VERSION[]{"1"}; | ||||
| 
 | ||||
| static std::unique_ptr<cpr::Session> g_session; | ||||
| constexpr int HTTP_PORT = 80; | ||||
| constexpr int HTTPS_PORT = 443; | ||||
| 
 | ||||
| void Win32WSAStartup() { | ||||
| #ifdef _WIN32 | ||||
|     // On Windows, CPR/libcurl does not properly initialize Winsock. The below code is used to
 | ||||
|     // initialize Winsock globally, which fixes this problem. Without this, only the first CPR
 | ||||
|     // session will properly be created, and subsequent ones will fail.
 | ||||
|     WSADATA wsa_data; | ||||
|     const int wsa_result{WSAStartup(MAKEWORD(2, 2), &wsa_data)}; | ||||
|     if (wsa_result) { | ||||
|         LOG_CRITICAL(WebService, "WSAStartup failed: %d", wsa_result); | ||||
| constexpr int TIMEOUT_SECONDS = 30; | ||||
| 
 | ||||
| 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, | ||||
|                                             hl::HttpVersion::v1_1); | ||||
|     } 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, | ||||
|                                                hl::HttpVersion::v1_1); | ||||
|     } else { | ||||
|         LOG_ERROR(WebService, "Bad URL scheme %s", parsedUrl.m_Scheme.c_str()); | ||||
|         return nullptr; | ||||
|     } | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| std::future<Common::WebResult> PostJson(const std::string& url, const std::string& data, | ||||
|                                         bool allow_anonymous, const std::string& username, | ||||
|                                         const std::string& token) { | ||||
|     if (url.empty()) { | ||||
|     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"}; | ||||
|  | @ -51,51 +69,71 @@ std::future<Common::WebResult> PostJson(const std::string& url, const std::strin | |||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     Win32WSAStartup(); | ||||
| 
 | ||||
|     // Built request header
 | ||||
|     cpr::Header header; | ||||
|     hl::Headers params; | ||||
|     if (are_credentials_provided) { | ||||
|         // Authenticated request if credentials are provided
 | ||||
|         header = {{"Content-Type", "application/json"}, | ||||
|                   {"x-username", username.c_str()}, | ||||
|                   {"x-token", token.c_str()}, | ||||
|                   {"api-version", API_VERSION}}; | ||||
|         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
 | ||||
|         header = cpr::Header{{"Content-Type", "application/json"}, {"api-version", API_VERSION}}; | ||||
|         params = {{std::string("api-version"), std::string(API_VERSION)}, | ||||
|                   {std::string("Content-Type"), std::string("application/json")}}; | ||||
|     } | ||||
| 
 | ||||
|     // Post JSON asynchronously
 | ||||
|     return cpr::PostCallback( | ||||
|         [](cpr::Response r) { | ||||
|             if (r.error) { | ||||
|                 LOG_ERROR(WebService, "POST to %s returned cpr error: %u:%s", r.url.c_str(), | ||||
|                           static_cast<u32>(r.error.code), r.error.message.c_str()); | ||||
|                 return Common::WebResult{Common::WebResult::Code::CprError, r.error.message}; | ||||
|             } | ||||
|             if (r.status_code >= 400) { | ||||
|                 LOG_ERROR(WebService, "POST to %s returned error status code: %u", r.url.c_str(), | ||||
|                           r.status_code); | ||||
|                 return Common::WebResult{Common::WebResult::Code::HttpError, | ||||
|                                          std::to_string(r.status_code)}; | ||||
|             } | ||||
|             if (r.header["content-type"].find("application/json") == std::string::npos) { | ||||
|                 LOG_ERROR(WebService, "POST to %s returned wrong content: %s", r.url.c_str(), | ||||
|                           r.header["content-type"].c_str()); | ||||
|                 return Common::WebResult{Common::WebResult::Code::WrongContent, | ||||
|                                          r.header["content-type"]}; | ||||
|             } | ||||
|             return Common::WebResult{Common::WebResult::Code::Success, ""}; | ||||
|         }, | ||||
|         cpr::Url{url}, cpr::Body{data}, header); | ||||
|     return std::async(std::launch::async, [url, parsedUrl, params, data] { | ||||
|         std::unique_ptr<hl::Client> cli = GetClientFor(parsedUrl); | ||||
| 
 | ||||
|         if (cli == nullptr) { | ||||
|             return Common::WebResult{Common::WebResult::Code::InvalidURL, "URL is invalid"}; | ||||
|         } | ||||
| 
 | ||||
|         hl::Request request; | ||||
|         request.method = "POST"; | ||||
|         request.path = "/" + parsedUrl.m_Path; | ||||
|         request.headers = params; | ||||
|         request.body = data; | ||||
| 
 | ||||
|         hl::Response response; | ||||
| 
 | ||||
|         if (!cli->send(request, response)) { | ||||
|             LOG_ERROR(WebService, "POST to %s returned null", url.c_str()); | ||||
|             return Common::WebResult{Common::WebResult::Code::LibError, "Null response"}; | ||||
|         } | ||||
| 
 | ||||
|         if (response.status >= 400) { | ||||
|             LOG_ERROR(WebService, "POST to %s returned error status code: %u", url.c_str(), | ||||
|                       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) { | ||||
|             LOG_ERROR(WebService, "POST to %s returned wrong content: %s", url.c_str(), | ||||
|                       content_type->second.c_str()); | ||||
|             return Common::WebResult{Common::WebResult::Code::WrongContent, content_type->second}; | ||||
|         } | ||||
| 
 | ||||
|         return Common::WebResult{Common::WebResult::Code::Success, ""}; | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| std::future<T> GetJson(std::function<T(const std::string&)> func, const std::string& url, | ||||
|                        bool allow_anonymous, const std::string& username, | ||||
|                        const std::string& token) { | ||||
|     if (url.empty()) { | ||||
|     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(""); }); | ||||
|     } | ||||
|  | @ -106,42 +144,55 @@ std::future<T> GetJson(std::function<T(const std::string&)> func, const std::str | |||
|         return std::async(std::launch::deferred, [func{std::move(func)}]() { return func(""); }); | ||||
|     } | ||||
| 
 | ||||
|     Win32WSAStartup(); | ||||
| 
 | ||||
|     // Built request header
 | ||||
|     cpr::Header header; | ||||
|     hl::Headers params; | ||||
|     if (are_credentials_provided) { | ||||
|         // Authenticated request if credentials are provided
 | ||||
|         header = {{"Content-Type", "application/json"}, | ||||
|                   {"x-username", username.c_str()}, | ||||
|                   {"x-token", token.c_str()}, | ||||
|                   {"api-version", API_VERSION}}; | ||||
|         params = {{std::string("x-username"), username}, | ||||
|                   {std::string("x-token"), token}, | ||||
|                   {std::string("api-version"), std::string(API_VERSION)}}; | ||||
|     } else { | ||||
|         // Otherwise, anonymous request
 | ||||
|         header = cpr::Header{{"Content-Type", "application/json"}, {"api-version", API_VERSION}}; | ||||
|         params = {{std::string("api-version"), std::string(API_VERSION)}}; | ||||
|     } | ||||
| 
 | ||||
|     // Get JSON asynchronously
 | ||||
|     return cpr::GetCallback( | ||||
|         [func{std::move(func)}](cpr::Response r) { | ||||
|             if (r.error) { | ||||
|                 LOG_ERROR(WebService, "GET to %s returned cpr error: %u:%s", r.url.c_str(), | ||||
|                           static_cast<u32>(r.error.code), r.error.message.c_str()); | ||||
|                 return func(""); | ||||
|             } | ||||
|             if (r.status_code >= 400) { | ||||
|                 LOG_ERROR(WebService, "GET to %s returned error code: %u", r.url.c_str(), | ||||
|                           r.status_code); | ||||
|                 return func(""); | ||||
|             } | ||||
|             if (r.header["content-type"].find("application/json") == std::string::npos) { | ||||
|                 LOG_ERROR(WebService, "GET to %s returned wrong content: %s", r.url.c_str(), | ||||
|                           r.header["content-type"].c_str()); | ||||
|                 return func(""); | ||||
|             } | ||||
|             return func(r.text); | ||||
|         }, | ||||
|         cpr::Url{url}, header); | ||||
|     return std::async(std::launch::async, [func, url, parsedUrl, params] { | ||||
|         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 %s returned null", url.c_str()); | ||||
|             return func(""); | ||||
|         } | ||||
| 
 | ||||
|         if (response.status >= 400) { | ||||
|             LOG_ERROR(WebService, "GET to %s returned error status code: %u", url.c_str(), | ||||
|                       response.status); | ||||
|             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 %s returned wrong content: %s", url.c_str(), | ||||
|                       content_type->second.c_str()); | ||||
|             return func(""); | ||||
|         } | ||||
| 
 | ||||
|         return func(response.body); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| template std::future<bool> GetJson(std::function<bool(const std::string&)> func, | ||||
|  | @ -154,45 +205,66 @@ template std::future<AnnounceMultiplayerRoom::RoomList> GetJson( | |||
| 
 | ||||
| void DeleteJson(const std::string& url, const std::string& data, const std::string& username, | ||||
|                 const std::string& token) { | ||||
|     if (url.empty()) { | ||||
|     using lup = LUrlParser::clParseURL; | ||||
|     namespace hl = httplib; | ||||
| 
 | ||||
|     lup parsedUrl = lup::ParseURL(url); | ||||
| 
 | ||||
|     if (url.empty() || !parsedUrl.IsValid()) { | ||||
|         LOG_ERROR(WebService, "URL is invalid"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (token.empty() || username.empty()) { | ||||
|     const bool are_credentials_provided{!token.empty() && !username.empty()}; | ||||
|     if (!are_credentials_provided) { | ||||
|         LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Win32WSAStartup(); | ||||
| 
 | ||||
|     // Built request header
 | ||||
|     cpr::Header header = {{"Content-Type", "application/json"}, | ||||
|                           {"x-username", username.c_str()}, | ||||
|                           {"x-token", token.c_str()}, | ||||
|                           {"api-version", API_VERSION}}; | ||||
|     hl::Headers 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")}}; | ||||
| 
 | ||||
|     // Delete JSON asynchronously
 | ||||
|     static std::future<void> future; | ||||
|     future = cpr::DeleteCallback( | ||||
|         [](cpr::Response r) { | ||||
|             if (r.error) { | ||||
|                 LOG_ERROR(WebService, "Delete to %s returned cpr error: %u:%s", r.url.c_str(), | ||||
|                           static_cast<u32>(r.error.code), r.error.message.c_str()); | ||||
|                 return; | ||||
|             } | ||||
|             if (r.status_code >= 400) { | ||||
|                 LOG_ERROR(WebService, "Delete to %s returned error status code: %u", r.url.c_str(), | ||||
|                           r.status_code); | ||||
|                 return; | ||||
|             } | ||||
|             if (r.header["content-type"].find("application/json") == std::string::npos) { | ||||
|                 LOG_ERROR(WebService, "Delete to %s returned wrong content: %s", r.url.c_str(), | ||||
|                           r.header["content-type"].c_str()); | ||||
|                 return; | ||||
|             } | ||||
|         }, | ||||
|         cpr::Url{url}, cpr::Body{data}, header); | ||||
|     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 %s returned null", url.c_str()); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (response.status >= 400) { | ||||
|             LOG_ERROR(WebService, "DELETE to %s returned error status code: %u", url.c_str(), | ||||
|                       response.status); | ||||
|             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 %s returned wrong content: %s", url.c_str(), | ||||
|                       content_type->second.c_str()); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         return; | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| } // namespace WebService
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue