From d6668713d145a63aaf28444ad6fe6fc08b096d83 Mon Sep 17 00:00:00 2001 From: john30 Date: Sun, 28 Jan 2018 15:12:59 +0100 Subject: [PATCH] extracted http client --- src/ebusd/mainloop.cpp | 81 ++++----------------- src/lib/utils/CMakeLists.txt | 17 ++--- src/lib/utils/Makefile.am | 16 ++--- src/lib/utils/httpclient.cpp | 132 +++++++++++++++++++++++++++++++++++ src/lib/utils/httpclient.h | 111 +++++++++++++++++++++++++++++ 5 files changed, 270 insertions(+), 87 deletions(-) mode change 100644 => 100755 src/ebusd/mainloop.cpp mode change 100644 => 100755 src/lib/utils/CMakeLists.txt mode change 100644 => 100755 src/lib/utils/Makefile.am create mode 100755 src/lib/utils/httpclient.cpp create mode 100755 src/lib/utils/httpclient.h diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp old mode 100644 new mode 100755 index 8051734e..1bb10dc3 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -26,6 +26,7 @@ #include #include "ebusd/main.h" #include "lib/utils/log.h" +#include "lib/utils/httpclient.h" #include "lib/ebus/data.h" namespace ebusd { @@ -301,13 +302,12 @@ void MainLoop::run() { } } if (m_runUpdateCheck && !m_shutdown && now > nextCheckRun) { - TCPClient client; - TCPSocket* socket = client.connect("ebusd.eu", 80); - if (socket) { - socket->setTimeout(5); + HttpClient client; + if (!client.connect("ebusd.eu", 80, PACKAGE_NAME "/" PACKAGE_VERSION)) { + logError(lf_main, "update check connect error"); + } else { ostringstream ostr; - ostr << "{\"v\":\"" << PACKAGE_VERSION "\"" - << ",\"r\":\"" << REVISION << "\"" + ostr << "{\"v\":\"" PACKAGE_VERSION "\",\"r\":\"" REVISION << "\"" #if defined(__amd64__) || defined(__x86_64__) || defined(__ia64__) || defined(__IA64__) << ",\"a\":\"amd64\"" #elif defined(__aarch64__) @@ -327,69 +327,18 @@ void MainLoop::run() { } m_busHandler->formatUpdateInfo(&ostr); ostr << "}"; - string str = ostr.str(); - ostr.clear(); - ostr.str(""); - ostr << "POST /updatecheck/ HTTP/1.0\r\n" - << "Host: ebusd.eu" << "\r\n" - << "User-Agent: " << PACKAGE_NAME << "/" << PACKAGE_VERSION << "\r\n" - << "Content-Type: application/json; charset=utf-8\r\n" - << "Content-Length: " << dec << str.length() << "\r\n" - << "\r\n" - << str; - str = ostr.str(); - const char* cstr = str.c_str(); - size_t len = str.size(); - for (size_t pos = 0; pos < len; ) { - ssize_t sent = socket->send(cstr + pos, len - pos); - if (sent < 0) { - len = 0; - logError(lf_main, "update check send error"); - break; - } - pos += sent; - } - if (len) { - char buf[512]; - ssize_t received = socket->recv(buf, sizeof(buf)); - string result; - if (received > 15) { // "HTTP/1.1 200 OK" - buf[received - (received == sizeof(buf) ? 1 : 0)] = 0; - result = string(buf); - } - if (result.substr(0, 5) == "HTTP/") { - string message; - size_t pos = result.find("\r\n\r\n"); - if (pos != string::npos) { - message = result.substr(pos+4); + string response; + if (!client.post("/updatecheck/", ostr.str(), response)) { + logError(lf_main, "update check error: %s", response.c_str()); + } else { + m_updateCheck = response.empty() ? "unknown" : response; + logNotice(lf_main, "update check: %s", response.c_str()); + if (!dataSinks.empty()) { + for (const auto dataSink : dataSinks) { + dataSink->notifyUpdateCheckResult(response == "OK" ? "" : m_updateCheck); } - pos = result.find(" "); - if (pos != string::npos) { - result = result.substr(pos+1); - } - pos = result.find("\r\n"); - if (pos != string::npos) { - result = result.substr(0, pos); - } - if (result == "200 OK") { - m_updateCheck = message == "" ? "unknown" : message; - logNotice(lf_main, "update check: %s", message.c_str()); - if (!dataSinks.empty()) { - for (const auto dataSink : dataSinks) { - dataSink->notifyUpdateCheckResult(message == "OK" ? "" : m_updateCheck); - } - } - } else { - logError(lf_main, "update check error: %s", result.c_str()); - } - } else { - logError(lf_main, "update check receive error"); } } - delete socket; - socket = NULL; - } else { - logError(lf_main, "update check connect error"); } nextCheckRun = now + CHECK_DELAY; } diff --git a/src/lib/utils/CMakeLists.txt b/src/lib/utils/CMakeLists.txt old mode 100644 new mode 100755 index 9257e331..62e22159 --- a/src/lib/utils/CMakeLists.txt +++ b/src/lib/utils/CMakeLists.txt @@ -1,18 +1,13 @@ add_definitions(-Wconversion) set(libutils_a_SOURCES - log.cpp - log.h - tcpsocket.cpp - tcpsocket.h - thread.cpp - thread.h - clock.cpp - clock.h + log.h log.cpp + tcpsocket.h tcpsocket.cpp + thread.h thread.cpp + clock.h clock.cpp queue.h notify.h - rotatefile.cpp - rotatefile.h -) + rotatefile.h rotatefile.cpp + httpclient.h httpclient.cpp) add_library(utils ${libutils_a_SOURCES}) diff --git a/src/lib/utils/Makefile.am b/src/lib/utils/Makefile.am old mode 100644 new mode 100755 index b6d0526a..561d1b04 --- a/src/lib/utils/Makefile.am +++ b/src/lib/utils/Makefile.am @@ -4,18 +4,14 @@ AM_CXXFLAGS = -I$(top_srcdir)/src \ noinst_LIBRARIES = libutils.a -libutils_a_SOURCES = log.cpp \ - log.h \ - tcpsocket.cpp \ - tcpsocket.h \ - thread.cpp \ - thread.h \ - clock.cpp \ - clock.h \ +libutils_a_SOURCES = log.h log.cpp \ + tcpsocket.h tcpsocket.cpp \ + thread.h thread.cpp \ + clock.h clock.cpp \ queue.h \ notify.h \ - rotatefile.cpp \ - rotatefile.h + rotatefile.h rotatefile.cpp \ + httpclient.h httpclient.cpp distclean-local: -rm -f Makefile.in diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp new file mode 100755 index 00000000..a10d3998 --- /dev/null +++ b/src/lib/utils/httpclient.cpp @@ -0,0 +1,132 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2018 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "lib/utils/httpclient.h" +#include +#include +#include +#include + +namespace ebusd { + +using std::string; +using std::ostringstream; +using std::dec; +using std::hex; + +bool HttpClient::connect(const string& host, const uint16_t port, const string& userAgent, const int timeout) { + if (m_socket) { + delete m_socket; + m_socket = nullptr; + } + TCPSocket* socket = m_client.connect(host, port); + if (!socket) { + return false; + } + socket->setTimeout(timeout); + m_socket = socket; + m_host = host; + m_userAgent = userAgent; + return true; +} + +bool HttpClient::get(const string& uri, const string& body, string& response) { + return request("GET", uri, body, response); +} + +bool HttpClient::post(const string& uri, const string& body, string& response) { + return request("POST", uri, body, response); +} + +bool HttpClient::request(const string& method, const string& uri, const string& body, string& response) { + if (!m_socket) { + response = "not connected"; + return false; + } + ostringstream ostr; + ostr << method << " " << uri << " HTTP/1.0\r\n" + << "Host: " << m_host << "\r\n"; + if (!m_userAgent.empty()) { + ostr << "User-Agent: " << m_userAgent << "\r\n"; + } + if (body.empty()) { + ostr << "\r\n"; + } else { + ostr << "Content-Type: application/json; charset=utf-8\r\n" + << "Content-Length: " << dec << body.length() << "\r\n" + << "\r\n" + << body; + } + string str = ostr.str(); + size_t len = str.size(); + const char* cstr = str.c_str(); + for (size_t pos = 0; pos < len; ) { + ssize_t sent = m_socket->send(cstr + pos, len - pos); + if (sent < 0) { + response = "send error"; + return false; + } + pos += sent; + } + if (!m_buffer) { + m_buffer = (char *) malloc(1024); + if (!m_buffer) { + response = "memory allocation"; + return false; + } + m_bufferSize = 1024; + } + ssize_t received = m_socket->recv(m_buffer, m_bufferSize); + if (received <= 0) { + response = "receive error"; + return false; + } + string result = string(m_buffer, 0, static_cast(received)); // expect "HTTP/1.1 200 OK" + size_t pos = result.find(' '); + if (result.substr(0, 5) != "HTTP/" || pos == string::npos || pos > 8) { + response = "receive error (headers)"; + return false; + } + if (result.substr(pos+1, 6) != "200 OK") { + size_t endpos = result.find("\r\n", pos+1); + response = "receive error: " + result.substr(pos+1, endpos == string::npos ? endpos : endpos-pos-1); + return false; + } + pos = result.find("\r\n\r\n"); + while (pos == string::npos && result.length() < 256*1024) { + received = m_socket->recv(m_buffer, m_bufferSize); + if (received < 0) { + response = "receive error"; + return false; + } + if (received == 0) { + break; + } + size_t oldLength = result.length(); + result += string(m_buffer, 0, static_cast(received)); + pos = result.find("\r\n\r\n", oldLength - 3); + } + if (pos == string::npos) { + response = "receive error (headers)"; + return false; + } + response = result.substr(pos+4); + return true; +} + +} // namespace ebusd diff --git a/src/lib/utils/httpclient.h b/src/lib/utils/httpclient.h new file mode 100755 index 00000000..eae04a9e --- /dev/null +++ b/src/lib/utils/httpclient.h @@ -0,0 +1,111 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2018 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIB_UTILS_HTTP_H_ +#define LIB_UTILS_HTTP_H_ + +#include +#include +#include +#include "lib/utils/tcpsocket.h" + + +/** typedef for referencing @a sockaddr_in within namespace. */ +typedef struct sockaddr_in socketaddress; + +namespace ebusd { + +/** \file lib/utils/http.h */ + +using std::string; +using std::ifstream; + +/** + * Helper class for handling HTTP client requests. + */ +class HttpClient { + + public: + /** + * Constructor. + */ + HttpClient() : m_socket(nullptr), m_bufferSize(0), m_buffer(nullptr) {} + + /** + * Destructor. + */ + ~HttpClient() { + if (m_socket) { + delete m_socket; + m_socket = nullptr; + } + if (m_buffer) { + free(m_buffer); + m_buffer = nullptr; + } + } + + /** + * Connect to the specified server. + * @param host the host name to connect to. + * @param port the port to connect to. + * @param timeout the timeout in seconds, defaults to 5 seconds. + * @param userAgent the optional user agent to send in the request header. + * @return true on success, false on connect failure. + */ + bool connect(const string& host, uint16_t port, const string& userAgent = "", int timeout = 5); + + /** + * Execute a GET request. + * @param uri the URI string. + * @param body the optional body to send. + * @param response the response body from the server (or the HTTP header on error). + * @return true on success, false on error. + */ + bool get(const string& uri, const string& body, string& response); + + /** + * Execute a POST request. + * @param uri the URI string. + * @param body the optional body to send. + * @param response the response body from the server (or the HTTP header on error). + * @return true on success, false on error. + */ + bool post(const string& uri, const string& body, string& response); + + /** + * Execute an arbitrary request. + * @param uri the URI string. + * @param body the optional body to send. + * @param response the response body from the server (or the HTTP header on error). + * @return true on success, false on error. + */ + bool request(const string& method, const string& uri, const string& body, string& response); + +private: + TCPClient m_client; + string m_host; + string m_userAgent; + TCPSocket* m_socket; + size_t m_bufferSize; + char* m_buffer; +}; + +} // namespace ebusd + +#endif //LIB_UTILS_HTTP_H_