From 18678afa6de918a17800a27dfc85027eb717facd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Mon, 12 Jun 2017 17:17:05 +0200 Subject: Common: Add HttpRequest to simplify HTTP requests Too much boilerplate that is duplicated if we use curl directly. Let's add a simple wrapper class that hides the implementation details and just allows to simply make HTTP requests and get responses. --- Source/Core/Common/HttpRequest.cpp | 120 +++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 Source/Core/Common/HttpRequest.cpp (limited to 'Source/Core/Common/HttpRequest.cpp') diff --git a/Source/Core/Common/HttpRequest.cpp b/Source/Core/Common/HttpRequest.cpp new file mode 100644 index 0000000000..68b5036ee9 --- /dev/null +++ b/Source/Core/Common/HttpRequest.cpp @@ -0,0 +1,120 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Common/HttpRequest.h" + +#include +#include + +#include "Common/Logging/Log.h" + +namespace Common +{ +class HttpRequest::Impl final +{ +public: + enum class Method + { + GET, + POST, + }; + + Impl(); + + bool IsValid() const; + Response Fetch(const std::string& url, Method method, const u8* payload, size_t size); + +private: + std::unique_ptr m_curl{curl_easy_init(), curl_easy_cleanup}; +}; + +HttpRequest::HttpRequest() : m_impl(std::make_unique()) +{ +} + +HttpRequest::~HttpRequest() = default; + +bool HttpRequest::IsValid() const +{ + return m_impl->IsValid(); +} + +HttpRequest::Response HttpRequest::Get(const std::string& url) +{ + return m_impl->Fetch(url, Impl::Method::GET, nullptr, 0); +} + +HttpRequest::Response HttpRequest::Post(const std::string& url, const std::vector& payload) +{ + return m_impl->Fetch(url, Impl::Method::POST, payload.data(), payload.size()); +} + +HttpRequest::Response HttpRequest::Post(const std::string& url, const std::string& payload) +{ + return m_impl->Fetch(url, Impl::Method::POST, reinterpret_cast(payload.data()), + payload.size()); +} + +HttpRequest::Impl::Impl() +{ + if (!m_curl) + return; + + // libcurl may not have been built with async DNS support, so we disable + // signal handlers to avoid a possible and likely crash if a resolve times out. + curl_easy_setopt(m_curl.get(), CURLOPT_NOSIGNAL, true); + curl_easy_setopt(m_curl.get(), CURLOPT_TIMEOUT, 3); +#ifdef _WIN32 + // ALPN support is enabled by default but requires Windows >= 8.1. + curl_easy_setopt(m_curl.get(), CURLOPT_SSL_ENABLE_ALPN, false); +#endif +} + +bool HttpRequest::Impl::IsValid() const +{ + return m_curl != nullptr; +} + +static size_t CurlCallback(char* data, size_t size, size_t nmemb, void* userdata) +{ + auto* buffer = static_cast*>(userdata); + const size_t actual_size = size * nmemb; + buffer->insert(buffer->end(), data, data + actual_size); + return actual_size; +} + +HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method method, + const u8* payload, size_t size) +{ + curl_easy_setopt(m_curl.get(), CURLOPT_POST, method == Method::POST); + curl_easy_setopt(m_curl.get(), CURLOPT_URL, url.c_str()); + if (method == Method::POST) + { + curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDS, payload); + curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDSIZE, size); + } + + std::vector buffer; + curl_easy_setopt(m_curl.get(), CURLOPT_WRITEFUNCTION, CurlCallback); + curl_easy_setopt(m_curl.get(), CURLOPT_WRITEDATA, &buffer); + + const char* type = method == Method::POST ? "POST" : "GET"; + const CURLcode res = curl_easy_perform(m_curl.get()); + if (res != CURLE_OK) + { + ERROR_LOG(COMMON, "Failed to %s %s: %s", type, url.c_str(), curl_easy_strerror(res)); + return {}; + } + + long response_code = 0; + curl_easy_getinfo(m_curl.get(), CURLINFO_RESPONSE_CODE, &response_code); + if (response_code != 200) + { + ERROR_LOG(COMMON, "Failed to %s %s: response code was %li", type, url.c_str(), response_code); + return {}; + } + + return buffer; +} +} // namespace Common -- cgit v1.2.3 From 8f87433719c58c60df4cb514e9e0eb215fb65158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Tue, 13 Jun 2017 12:23:08 +0200 Subject: HttpRequest: Log response body on failure --- Source/Core/Common/HttpRequest.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Source/Core/Common/HttpRequest.cpp') diff --git a/Source/Core/Common/HttpRequest.cpp b/Source/Core/Common/HttpRequest.cpp index 68b5036ee9..0f4c1695d1 100644 --- a/Source/Core/Common/HttpRequest.cpp +++ b/Source/Core/Common/HttpRequest.cpp @@ -111,7 +111,8 @@ HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method me curl_easy_getinfo(m_curl.get(), CURLINFO_RESPONSE_CODE, &response_code); if (response_code != 200) { - ERROR_LOG(COMMON, "Failed to %s %s: response code was %li", type, url.c_str(), response_code); + ERROR_LOG(COMMON, "Failed to %s %s: server replied with code %li and body\n\x1b[0m%s", type, + url.c_str(), response_code, buffer.data()); return {}; } -- cgit v1.2.3 From ba3f16edbfe8b152f12a66b8fb3da215c8e52a1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Tue, 13 Jun 2017 12:52:10 +0200 Subject: HttpRequest: Add support for sending custom headers --- Source/Core/Common/HttpRequest.cpp | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) (limited to 'Source/Core/Common/HttpRequest.cpp') diff --git a/Source/Core/Common/HttpRequest.cpp b/Source/Core/Common/HttpRequest.cpp index 0f4c1695d1..5c83521972 100644 --- a/Source/Core/Common/HttpRequest.cpp +++ b/Source/Core/Common/HttpRequest.cpp @@ -8,6 +8,8 @@ #include #include "Common/Logging/Log.h" +#include "Common/ScopeGuard.h" +#include "Common/StringUtil.h" namespace Common { @@ -23,7 +25,8 @@ public: Impl(); bool IsValid() const; - Response Fetch(const std::string& url, Method method, const u8* payload, size_t size); + Response Fetch(const std::string& url, Method method, const Headers& headers, const u8* payload, + size_t size); private: std::unique_ptr m_curl{curl_easy_init(), curl_easy_cleanup}; @@ -40,20 +43,22 @@ bool HttpRequest::IsValid() const return m_impl->IsValid(); } -HttpRequest::Response HttpRequest::Get(const std::string& url) +HttpRequest::Response HttpRequest::Get(const std::string& url, const Headers& headers) { - return m_impl->Fetch(url, Impl::Method::GET, nullptr, 0); + return m_impl->Fetch(url, Impl::Method::GET, headers, nullptr, 0); } -HttpRequest::Response HttpRequest::Post(const std::string& url, const std::vector& payload) +HttpRequest::Response HttpRequest::Post(const std::string& url, const std::vector& payload, + const Headers& headers) { - return m_impl->Fetch(url, Impl::Method::POST, payload.data(), payload.size()); + return m_impl->Fetch(url, Impl::Method::POST, headers, payload.data(), payload.size()); } -HttpRequest::Response HttpRequest::Post(const std::string& url, const std::string& payload) +HttpRequest::Response HttpRequest::Post(const std::string& url, const std::string& payload, + const Headers& headers) { - return m_impl->Fetch(url, Impl::Method::POST, reinterpret_cast(payload.data()), - payload.size()); + return m_impl->Fetch(url, Impl::Method::POST, headers, + reinterpret_cast(payload.data()), payload.size()); } HttpRequest::Impl::Impl() @@ -85,7 +90,8 @@ static size_t CurlCallback(char* data, size_t size, size_t nmemb, void* userdata } HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method method, - const u8* payload, size_t size) + const Headers& headers, const u8* payload, + size_t size) { curl_easy_setopt(m_curl.get(), CURLOPT_POST, method == Method::POST); curl_easy_setopt(m_curl.get(), CURLOPT_URL, url.c_str()); @@ -95,6 +101,19 @@ HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method me curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDSIZE, size); } + curl_slist* list = nullptr; + Common::ScopeGuard list_guard{[&list] { curl_slist_free_all(list); }}; + for (const std::pair>& header : headers) + { + if (!header.second) + list = curl_slist_append(list, (header.first + ":").c_str()); + else if (header.second->empty()) + list = curl_slist_append(list, (header.first + ";").c_str()); + else + list = curl_slist_append(list, (header.first + ": " + *header.second).c_str()); + } + curl_easy_setopt(m_curl.get(), CURLOPT_HTTPHEADER, list); + std::vector buffer; curl_easy_setopt(m_curl.get(), CURLOPT_WRITEFUNCTION, CurlCallback); curl_easy_setopt(m_curl.get(), CURLOPT_WRITEDATA, &buffer); -- cgit v1.2.3 From 0d58a0bfe2f75c4ea354d0ea4bfa64514dca47ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Tue, 13 Jun 2017 19:17:11 +0200 Subject: HttpRequest: Add support for custom timeouts --- Source/Core/Common/HttpRequest.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'Source/Core/Common/HttpRequest.cpp') diff --git a/Source/Core/Common/HttpRequest.cpp b/Source/Core/Common/HttpRequest.cpp index 5c83521972..b87ce4fb98 100644 --- a/Source/Core/Common/HttpRequest.cpp +++ b/Source/Core/Common/HttpRequest.cpp @@ -22,7 +22,7 @@ public: POST, }; - Impl(); + Impl(int timeout_ms); bool IsValid() const; Response Fetch(const std::string& url, Method method, const Headers& headers, const u8* payload, @@ -32,7 +32,7 @@ private: std::unique_ptr m_curl{curl_easy_init(), curl_easy_cleanup}; }; -HttpRequest::HttpRequest() : m_impl(std::make_unique()) +HttpRequest::HttpRequest(int timeout_ms) : m_impl(std::make_unique(timeout_ms)) { } @@ -61,7 +61,7 @@ HttpRequest::Response HttpRequest::Post(const std::string& url, const std::strin reinterpret_cast(payload.data()), payload.size()); } -HttpRequest::Impl::Impl() +HttpRequest::Impl::Impl(int timeout_ms) { if (!m_curl) return; @@ -69,7 +69,7 @@ HttpRequest::Impl::Impl() // libcurl may not have been built with async DNS support, so we disable // signal handlers to avoid a possible and likely crash if a resolve times out. curl_easy_setopt(m_curl.get(), CURLOPT_NOSIGNAL, true); - curl_easy_setopt(m_curl.get(), CURLOPT_TIMEOUT, 3); + curl_easy_setopt(m_curl.get(), CURLOPT_TIMEOUT_MS, timeout_ms); #ifdef _WIN32 // ALPN support is enabled by default but requires Windows >= 8.1. curl_easy_setopt(m_curl.get(), CURLOPT_SSL_ENABLE_ALPN, false); -- cgit v1.2.3