summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEgor Pugin <egor.pugin@gmail.com>2018-07-04 19:11:03 +0300
committerEgor Pugin <egor.pugin@gmail.com>2018-07-04 19:11:03 +0300
commit95f86f4ce18e65d45894d5a130c4bdaf8ecbaac5 (patch)
tree0235eaef9d2dcd9725c1eb9d24c8de0201ba6fcc
parent7f388398bbcff916f5507770af727ef9ad59f33d (diff)
Implement CurlHttpClient.
-rw-r--r--CMakeLists.txt8
-rw-r--r--include/tgbot/Api.h4
-rw-r--r--include/tgbot/Bot.h5
-rw-r--r--include/tgbot/net/HttpClient.h64
-rw-r--r--src/Api.cpp7
-rw-r--r--src/net/HttpClient.cpp66
6 files changed, 143 insertions, 11 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f5ff1d2..236acbb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -31,6 +31,13 @@ find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED)
include_directories(${OPENSSL_INCLUDE_DIR})
+# curl
+find_package(CURL)
+if (CURL_FOUND)
+include_directories(${CURL_INCLUDE_DIRS})
+add_definitions(-DHAVE_CURL)
+endif()
+
# boost
set(Boost_USE_MULTITHREADED ON)
if (ENABLE_TESTS)
@@ -44,6 +51,7 @@ set(LIB_LIST
${CMAKE_THREAD_LIBS_INIT}
${OPENSSL_LIBRARIES}
${Boost_LIBRARIES}
+ ${CURL_LIBRARIES}
)
### building project
diff --git a/include/tgbot/Api.h b/include/tgbot/Api.h
index 8dea56a..e73fbe1 100644
--- a/include/tgbot/Api.h
+++ b/include/tgbot/Api.h
@@ -28,6 +28,7 @@
#include <boost/property_tree/ptree.hpp>
+#include "tgbot/net/HttpClient.h"
#include "tgbot/net/HttpReqArg.h"
#include "tgbot/types/User.h"
#include "tgbot/types/Message.h"
@@ -62,7 +63,7 @@ typedef std::shared_ptr<std::vector<std::string>> StringArrayPtr;
friend class Bot;
public:
- Api(const std::string& token);
+ Api(const std::string& token, const HttpClient &httpClientDriver);
/**
* @brief A simple method for testing your bot's auth token.
@@ -890,6 +891,7 @@ private:
boost::property_tree::ptree sendRequest(const std::string& method, const std::vector<HttpReqArg>& args = std::vector<HttpReqArg>()) const;
const std::string _token;
+ const HttpClient &_httpClientDriver;
};
}
diff --git a/include/tgbot/Bot.h b/include/tgbot/Bot.h
index 8dd4c16..9bca05f 100644
--- a/include/tgbot/Bot.h
+++ b/include/tgbot/Bot.h
@@ -28,6 +28,7 @@
#include "tgbot/Api.h"
#include "tgbot/EventBroadcaster.h"
#include "tgbot/EventHandler.h"
+#include "tgbot/net/HttpClient.h"
namespace TgBot {
@@ -39,7 +40,8 @@ namespace TgBot {
class Bot {
public:
- explicit Bot(const std::string& token) : _token(token), _api(token), _eventHandler(&_eventBroadcaster) {
+ explicit Bot(const std::string& token, const HttpClient &httpClientDriver = BoostHttpClient::getInstance())
+ : _token(token), _api(token, httpClientDriver), _eventHandler(&_eventBroadcaster), _httpClientDriver(httpClientDriver) {
}
/**
@@ -75,6 +77,7 @@ private:
const Api _api;
EventBroadcaster _eventBroadcaster;
const EventHandler _eventHandler;
+ const HttpClient &_httpClientDriver;
};
}
diff --git a/include/tgbot/net/HttpClient.h b/include/tgbot/net/HttpClient.h
index 8d67891..1375785 100644
--- a/include/tgbot/net/HttpClient.h
+++ b/include/tgbot/net/HttpClient.h
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2015 Oleg Morozenkov
+ * Copyright (c) 2018 Egor Pugin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -26,6 +27,9 @@
#include <string>
#include <boost/asio.hpp>
+#ifdef HAVE_CURL
+#include <curl/curl.h>
+#endif
#include "tgbot/net/Url.h"
#include "tgbot/net/HttpReqArg.h"
@@ -41,10 +45,29 @@ namespace TgBot {
class HttpClient {
public:
+ virtual ~HttpClient() = default;
+
+ /**
+ * @brief Sends a request to the url.
+ *
+ * If there's no args specified, a GET request will be sent, otherwise a POST request will be sent.
+ * If at least 1 arg is marked as file, the content type of a request will be multipart/form-data, otherwise it will be application/x-www-form-urlencoded.
+ */
+ virtual std::string makeRequest(const Url& url, const std::vector<HttpReqArg>& args) const = 0;
+};
+
+/**
+ * @brief This class makes http requests via boost::asio.
+ *
+ * @ingroup net
+ */
+class BoostHttpClient : public HttpClient {
+
+public:
/**
* @brief Returns instance which lives during all application lifetime.
*/
- static HttpClient& getInstance();
+ static BoostHttpClient& getInstance();
/**
* @brief Sends a request to the url.
@@ -52,12 +75,47 @@ public:
* If there's no args specified, a GET request will be sent, otherwise a POST request will be sent.
* If at least 1 arg is marked as file, the content type of a request will be multipart/form-data, otherwise it will be application/x-www-form-urlencoded.
*/
- std::string makeRequest(const Url& url, const std::vector<HttpReqArg>& args);
+ std::string makeRequest(const Url& url, const std::vector<HttpReqArg>& args) const override;
private:
- boost::asio::io_service _ioService;
+ mutable boost::asio::io_service _ioService;
};
+#ifdef HAVE_CURL
+
+/**
+ * @brief This class makes http requests via libcurl.
+ *
+ * @ingroup net
+ */
+class CurlHttpClient : public HttpClient {
+
+public:
+
+ /**
+ * @brief Raw curl settings storage for fine tuning.
+ */
+ CURL* curlSettings;
+
+ CurlHttpClient();
+ ~CurlHttpClient();
+
+ /**
+ * @brief Returns instance which lives during all application lifetime.
+ */
+ static CurlHttpClient& getInstance();
+
+ /**
+ * @brief Sends a request to the url.
+ *
+ * If there's no args specified, a GET request will be sent, otherwise a POST request will be sent.
+ * If at least 1 arg is marked as file, the content type of a request will be multipart/form-data, otherwise it will be application/x-www-form-urlencoded.
+ */
+ std::string makeRequest(const Url& url, const std::vector<HttpReqArg>& args) const override;
+};
+
+#endif
+
}
#endif //TGBOT_HTTPCLIENT_H
diff --git a/src/Api.cpp b/src/Api.cpp
index 3d8d3f6..3bb200b 100644
--- a/src/Api.cpp
+++ b/src/Api.cpp
@@ -31,7 +31,8 @@ using namespace boost::property_tree;
namespace TgBot {
-Api::Api(const string& token) : _token(token) {
+Api::Api(const string& token, const HttpClient &httpClientDriver)
+ : _token(token), _httpClientDriver(httpClientDriver) {
}
User::Ptr Api::getMe() const {
@@ -1198,7 +1199,7 @@ ptree Api::sendRequest(const string& method, const vector<HttpReqArg>& args) con
url += "/";
url += method;
- string serverResponse = HttpClient::getInstance().makeRequest(url, args);
+ string serverResponse = _httpClientDriver.makeRequest(url, args);
if (!serverResponse.compare(0, 6, "<html>")) {
throw TgException("tgbot-cpp library have got html page instead of json response. Maybe you entered wrong bot token.");
}
@@ -1221,7 +1222,7 @@ string Api::downloadFile(const string& filePath, const std::vector<HttpReqArg>&
url += "/";
url += filePath;
- string serverResponse = HttpClient::getInstance().makeRequest(url, args);
+ string serverResponse = _httpClientDriver.makeRequest(url, args);
return serverResponse;
}
diff --git a/src/net/HttpClient.cpp b/src/net/HttpClient.cpp
index 340eb1e..479b72a 100644
--- a/src/net/HttpClient.cpp
+++ b/src/net/HttpClient.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2015 Oleg Morozenkov
+ * Copyright (c) 2018 Egor Pugin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -30,12 +31,12 @@ using namespace boost::asio::ip;
namespace TgBot {
-HttpClient& HttpClient::getInstance() {
- static HttpClient result;
+BoostHttpClient& BoostHttpClient::getInstance() {
+ static BoostHttpClient result;
return result;
}
-string HttpClient::makeRequest(const Url& url, const vector<HttpReqArg>& args) {
+string BoostHttpClient::makeRequest(const Url& url, const vector<HttpReqArg>& args) const {
ssl::context context(ssl::context::sslv23);
context.set_default_verify_paths();
@@ -87,4 +88,63 @@ string HttpClient::makeRequest(const Url& url, const vector<HttpReqArg>& args) {
return HttpParser::getInstance().parseResponse(response);
}
+#ifdef HAVE_CURL
+
+CurlHttpClient::CurlHttpClient() {
+ curlSettings = curl_easy_init();
+}
+
+CurlHttpClient::~CurlHttpClient() {
+ curl_easy_cleanup(curlSettings);
+}
+
+CurlHttpClient& CurlHttpClient::getInstance() {
+ static CurlHttpClient result;
+ return result;
+}
+
+static size_t curl_write_string(char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+ std::string &s = *(std::string *)userdata;
+ auto read = size * nmemb;
+ s.append(ptr, ptr + read);
+ return read;
+};
+
+string CurlHttpClient::makeRequest(const Url& url, const vector<HttpReqArg>& args) const {
+ // Copy settings for each call because we change CURLOPT_URL and other stuff.
+ // This also protects multithreaded case.
+ auto curl = curl_easy_duphandle(curlSettings);
+
+ auto u = url.protocol + "://" + url.host + url.path;
+ curl_easy_setopt(curl, CURLOPT_URL, u.c_str());
+
+ if (!args.empty())
+ {
+ std::string data;
+ for (auto &a : args)
+ data += a.name + "=" + a.value + "&";
+ data.resize(data.size() - 1);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
+ }
+
+ std::string response;
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_string);
+
+ auto res = curl_easy_perform(curl);
+ long http_code;
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
+ curl_easy_cleanup(curl);
+
+ if (res != CURLE_OK)
+ throw std::runtime_error("curl error: "s + curl_easy_strerror(res));
+ if (http_code != 200)
+ throw std::runtime_error("curl request returned with code = " + std::to_string(http_code));
+
+ return HttpParser::getInstance().parseResponse(response);
+}
+
+#endif
+
}