From feb806b39286737fb5dba91532305217d5d3ca80 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Tue, 24 Nov 2015 07:47:29 +0100 Subject: [PATCH] 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()); +}