diff options
-rw-r--r-- | CMakeLists.txt | 8 | ||||
-rw-r--r-- | include/tgbot/Api.h | 4 | ||||
-rw-r--r-- | include/tgbot/Bot.h | 5 | ||||
-rw-r--r-- | include/tgbot/net/HttpClient.h | 64 | ||||
-rw-r--r-- | src/Api.cpp | 7 | ||||
-rw-r--r-- | src/net/HttpClient.cpp | 66 |
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 + } |