From feb806b39286737fb5dba91532305217d5d3ca80 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Tue, 24 Nov 2015 07:47:29 +0100 Subject: [PATCH 1/5] Add HttpClient --- CMakeLists.txt | 2 ++ network/CurlHttpClient.cpp | 47 +++++++++++++++++++++++++++++ network/CurlHttpClient.h | 28 +++++++++++++++++ network/FakeHttpClient.cpp | 22 ++++++++++++++ network/FakeHttpClient.h | 26 ++++++++++++++++ network/HttpClient.cpp | 1 + network/HttpClient.h | 16 ++++++++++ test/network/CurlHttpClientTest.cpp | 32 ++++++++++++++++++++ test/network/FakeHttpClientTest.cpp | 39 ++++++++++++++++++++++++ 9 files changed, 213 insertions(+) create mode 100644 network/CurlHttpClient.cpp create mode 100644 network/CurlHttpClient.h create mode 100644 network/FakeHttpClient.cpp create mode 100644 network/FakeHttpClient.h create mode 100644 network/HttpClient.cpp create mode 100644 network/HttpClient.h create mode 100644 test/network/CurlHttpClientTest.cpp create mode 100644 test/network/FakeHttpClientTest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b3da61af..74c0d436 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,8 @@ IF(${CMAKE_SYSTEM_NAME} MATCHES "Linux") TARGET_LINK_LIBRARIES(${BII_BLOCK_TARGET} INTERFACE rt) ENDIF(CMAKE_SYSTEM_NAME) +TARGET_LINK_LIBRARIES(${BII_BLOCK_TARGET} INTERFACE curl) + ENABLE_STYLE_WARNINGS() # You can safely delete lines from here... diff --git a/network/CurlHttpClient.cpp b/network/CurlHttpClient.cpp new file mode 100644 index 00000000..ad846f23 --- /dev/null +++ b/network/CurlHttpClient.cpp @@ -0,0 +1,47 @@ +// Base version taken from https://techoverflow.net/blog/2013/03/15/c-simple-http-download-using-libcurl-easy-api/ + +#include "CurlHttpClient.h" +#include +#include +#include +#include + +using boost::none; +using boost::optional; +using std::string; +using std::ostringstream; + +namespace cpputils { + + size_t CurlHttpClient::write_data(void *ptr, size_t size, size_t nmemb, ostringstream *stream) { + stream->write((const char *) ptr, size * nmemb); + return size * nmemb; + } + + CurlHttpClient::CurlHttpClient() { + curl = curl_easy_init(); + } + + CurlHttpClient::~CurlHttpClient() { + curl_easy_cleanup(curl); + } + + optional CurlHttpClient::get(const string &url) { + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + // example.com is redirected, so we tell libcurl to follow redirection + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); //Prevent "longjmp causes uninitialized stack frame" bug + curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "deflate"); + ostringstream out; + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &CurlHttpClient::write_data); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &out); + // Perform the request, res will get the return code + CURLcode res = curl_easy_perform(curl); + // Check for errors + if (res != CURLE_OK) { + return none; + } + return out.str(); + } + +} diff --git a/network/CurlHttpClient.h b/network/CurlHttpClient.h new file mode 100644 index 00000000..50f3e72d --- /dev/null +++ b/network/CurlHttpClient.h @@ -0,0 +1,28 @@ +#pragma once +#ifndef MESSMER_CPPUTILS_NETWORK_HTTPCLIENT_HPP +#define MESSMER_CPPUTILS_NETWORK_HTTPCLIENT_HPP + +#include "HttpClient.h" +#include "../macros.h" + +namespace cpputils { + + class CurlHttpClient final : public HttpClient { + public: + CurlHttpClient(); + + ~CurlHttpClient(); + + boost::optional get(const std::string &url) override; + + private: + void *curl; + + static size_t write_data(void *ptr, size_t size, size_t nmemb, std::ostringstream *stream); + + DISALLOW_COPY_AND_ASSIGN(CurlHttpClient); + }; + +} + +#endif diff --git a/network/FakeHttpClient.cpp b/network/FakeHttpClient.cpp new file mode 100644 index 00000000..fb81f82b --- /dev/null +++ b/network/FakeHttpClient.cpp @@ -0,0 +1,22 @@ +#include "FakeHttpClient.h" + +using std::string; +using boost::optional; +using boost::none; + +namespace cpputils { + FakeHttpClient::FakeHttpClient(): _sites() { + } + + void FakeHttpClient::addWebsite(const string &url, const string &content) { + _sites[url] = content; + } + + optional FakeHttpClient::get(const string &url) { + auto found = _sites.find(url); + if (found == _sites.end()) { + return none; + } + return found->second; + } +} diff --git a/network/FakeHttpClient.h b/network/FakeHttpClient.h new file mode 100644 index 00000000..5fd9e698 --- /dev/null +++ b/network/FakeHttpClient.h @@ -0,0 +1,26 @@ +#ifndef MESSMER_CPPUTILS_NETWORK_FAKEHTTPCLIENT_H +#define MESSMER_CPPUTILS_NETWORK_FAKEHTTPCLIENT_H + +#include "HttpClient.h" +#include "../macros.h" +#include + +namespace cpputils { + + class FakeHttpClient final : public HttpClient { + public: + FakeHttpClient(); + + void addWebsite(const std::string &url, const std::string &content); + + boost::optional get(const std::string &url) override; + + private: + std::map _sites; + + DISALLOW_COPY_AND_ASSIGN(FakeHttpClient); + }; + +} + +#endif diff --git a/network/HttpClient.cpp b/network/HttpClient.cpp new file mode 100644 index 00000000..1c7ca709 --- /dev/null +++ b/network/HttpClient.cpp @@ -0,0 +1 @@ +#include "HttpClient.h" diff --git a/network/HttpClient.h b/network/HttpClient.h new file mode 100644 index 00000000..8578f0d5 --- /dev/null +++ b/network/HttpClient.h @@ -0,0 +1,16 @@ +#ifndef MESSMER_CPPUTILS_NETWORK_HTTPCLIENT_H +#define MESSMER_CPPUTILS_NETWORK_HTTPCLIENT_H + +#include +#include + +namespace cpputils { + class HttpClient { + public: + virtual ~HttpClient() {} + + virtual boost::optional get(const std::string& url) = 0; + }; +}; + +#endif diff --git a/test/network/CurlHttpClientTest.cpp b/test/network/CurlHttpClientTest.cpp new file mode 100644 index 00000000..3c9b1a66 --- /dev/null +++ b/test/network/CurlHttpClientTest.cpp @@ -0,0 +1,32 @@ +#include +#include +#include "../../network/CurlHttpClient.h" +#include "../../pointer/unique_ref_boost_optional_gtest_workaround.h" + +using std::string; +using boost::none; +using testing::MatchesRegex; + +using namespace cpputils; + +TEST(CurlHttpClientTest, InvalidProtocol) { + EXPECT_EQ(none, CurlHttpClient().get("invalid://example.com")); +} + +TEST(CurlHttpClientTest, InvalidTld) { + EXPECT_EQ(none, CurlHttpClient().get("http://example.invalidtld")); +} + +TEST(CurlHttpClientTest, InvalidDomain) { + EXPECT_EQ(none, CurlHttpClient().get("http://this_is_a_not_existing_domain.com")); +} + +TEST(CurlHttpClientTest, ValidHttp) { + string content = CurlHttpClient().get("http://example.com").value(); + EXPECT_THAT(content, MatchesRegex(".*Example Domain.*")); +} + +TEST(CurlHttpClientTest, ValidHttps) { + string content = CurlHttpClient().get("https://example.com").value(); + EXPECT_THAT(content, MatchesRegex(".*Example Domain.*")); +} diff --git a/test/network/FakeHttpClientTest.cpp b/test/network/FakeHttpClientTest.cpp new file mode 100644 index 00000000..7c34a6c7 --- /dev/null +++ b/test/network/FakeHttpClientTest.cpp @@ -0,0 +1,39 @@ +#include +#include "../../network/FakeHttpClient.h" +#include "../../pointer/unique_ref_boost_optional_gtest_workaround.h" + +using boost::none; + +using namespace cpputils; + +TEST(FakeHttpClientTest, Empty) { + EXPECT_EQ(none, FakeHttpClient().get("http://example.com")); +} + +TEST(FakeHttpClientTest, Nonexisting) { + FakeHttpClient client; + client.addWebsite("http://existing.com", "content"); + EXPECT_EQ(none, client.get("http://notexisting.com")); +} + +TEST(FakeHttpClientTest, Existing) { + FakeHttpClient client; + client.addWebsite("http://existing.com", "content"); + EXPECT_EQ("content", client.get("http://existing.com").value()); +} + +TEST(FakeHttpClientTest, TwoExisting) { + FakeHttpClient client; + client.addWebsite("http://firstexisting.com", "first_content"); + client.addWebsite("http://secondexisting.com", "second_content"); + EXPECT_EQ("first_content", client.get("http://firstexisting.com").value()); + EXPECT_EQ("second_content", client.get("http://secondexisting.com").value()); + EXPECT_EQ(none, client.get("http://notexisting.com")); +} + +TEST(FakeHttpClientTest, Overwriting) { + FakeHttpClient client; + client.addWebsite("http://existing.com", "content"); + client.addWebsite("http://existing.com", "new_content"); + EXPECT_EQ("new_content", client.get("http://existing.com").value()); +} From 3fe90ea434f9f202276fbd196c04a78da6908b81 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Tue, 24 Nov 2015 08:23:20 +0100 Subject: [PATCH 2/5] Add timeout to HttpClient --- network/CurlHttpClient.cpp | 5 ++++- network/CurlHttpClient.h | 2 +- network/FakeHttpClient.cpp | 3 ++- network/FakeHttpClient.h | 2 +- network/HttpClient.h | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/network/CurlHttpClient.cpp b/network/CurlHttpClient.cpp index ad846f23..c80a0a62 100644 --- a/network/CurlHttpClient.cpp +++ b/network/CurlHttpClient.cpp @@ -26,7 +26,7 @@ namespace cpputils { curl_easy_cleanup(curl); } - optional CurlHttpClient::get(const string &url) { + optional CurlHttpClient::get(const string &url, optional timeoutMsec) { curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // example.com is redirected, so we tell libcurl to follow redirection curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); @@ -35,6 +35,9 @@ namespace cpputils { ostringstream out; curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &CurlHttpClient::write_data); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &out); + if (timeoutMsec != none) { + curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, *timeoutMsec); + } // Perform the request, res will get the return code CURLcode res = curl_easy_perform(curl); // Check for errors diff --git a/network/CurlHttpClient.h b/network/CurlHttpClient.h index 50f3e72d..d86831b7 100644 --- a/network/CurlHttpClient.h +++ b/network/CurlHttpClient.h @@ -13,7 +13,7 @@ namespace cpputils { ~CurlHttpClient(); - boost::optional get(const std::string &url) override; + boost::optional get(const std::string &url, boost::optional timeoutMsec = boost::none) override; private: void *curl; diff --git a/network/FakeHttpClient.cpp b/network/FakeHttpClient.cpp index fb81f82b..098ffa3d 100644 --- a/network/FakeHttpClient.cpp +++ b/network/FakeHttpClient.cpp @@ -12,7 +12,8 @@ namespace cpputils { _sites[url] = content; } - optional FakeHttpClient::get(const string &url) { + optional FakeHttpClient::get(const string &url, optional timeoutMsec) { + UNUSED(timeoutMsec); auto found = _sites.find(url); if (found == _sites.end()) { return none; diff --git a/network/FakeHttpClient.h b/network/FakeHttpClient.h index 5fd9e698..eb63d956 100644 --- a/network/FakeHttpClient.h +++ b/network/FakeHttpClient.h @@ -13,7 +13,7 @@ namespace cpputils { void addWebsite(const std::string &url, const std::string &content); - boost::optional get(const std::string &url) override; + boost::optional get(const std::string &url, boost::optional timeoutMsec = boost::none) override; private: std::map _sites; diff --git a/network/HttpClient.h b/network/HttpClient.h index 8578f0d5..6fc3b82b 100644 --- a/network/HttpClient.h +++ b/network/HttpClient.h @@ -9,7 +9,7 @@ namespace cpputils { public: virtual ~HttpClient() {} - virtual boost::optional get(const std::string& url) = 0; + virtual boost::optional get(const std::string& url, boost::optional timeoutMsec = boost::none) = 0; }; }; From ef49309782655d8931301a4e7240da223ca75019 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Tue, 24 Nov 2015 08:30:35 +0100 Subject: [PATCH 3/5] Update biicode block ref --- biicode.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/biicode.conf b/biicode.conf index b48524b1..746b553c 100644 --- a/biicode.conf +++ b/biicode.conf @@ -9,7 +9,7 @@ messmer/spdlog: 1 [parent] - messmer/cpp-utils: 6 + messmer/cpp-utils: 7 [paths] # Local directories to look for headers (within block) # / From d400550af7ef85d6cf68e198c3fd046e871c7dfa Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Tue, 24 Nov 2015 14:32:53 +0100 Subject: [PATCH 4/5] Code is compatible with GCC 4.8 --- pointer/gcc_4_8_compatibility.h | 18 ++++++++++++++++++ pointer/unique_ref.h | 1 + 2 files changed, 19 insertions(+) create mode 100644 pointer/gcc_4_8_compatibility.h diff --git a/pointer/gcc_4_8_compatibility.h b/pointer/gcc_4_8_compatibility.h new file mode 100644 index 00000000..bf228c13 --- /dev/null +++ b/pointer/gcc_4_8_compatibility.h @@ -0,0 +1,18 @@ +#pragma once +#ifndef MESSMER_CPPUTILS_GCC48COMPATIBILITY_H +#define MESSMER_CPPUTILS_GCC48COMPATIBILITY_H + +#include + +#if __GNUC__ == 4 && __GNUC_MINOR__ == 8 +// Add std::make_unique +namespace std { + template + inline unique_ptr make_unique(Args&&... args) { + return unique_ptr(new T(std::forward(args)...)); + } +} + +#endif + +#endif diff --git a/pointer/unique_ref.h b/pointer/unique_ref.h index bee77221..1cc2120f 100644 --- a/pointer/unique_ref.h +++ b/pointer/unique_ref.h @@ -5,6 +5,7 @@ #include #include #include "../macros.h" +#include "gcc_4_8_compatibility.h" #include "cast.h" namespace cpputils { From af9219a7bd2b0fcbc304ae571067bb463dd4980f Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Tue, 24 Nov 2015 14:38:03 +0100 Subject: [PATCH 5/5] Update dependencies --- biicode.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/biicode.conf b/biicode.conf index 746b553c..cef6e318 100644 --- a/biicode.conf +++ b/biicode.conf @@ -5,7 +5,7 @@ google/gmock: 4 google/gtest: 11 messmer/cmake: 3 - messmer/scrypt: 0 + messmer/scrypt: 1 messmer/spdlog: 1 [parent]