extracted http client

This commit is contained in:
john30
2018-01-28 15:12:59 +01:00
parent 472c25fb0c
commit d6668713d1
5 changed files with 270 additions and 87 deletions
Regular → Executable
+15 -66
View File
@@ -26,6 +26,7 @@
#include <algorithm>
#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;
}
Regular → Executable
+6 -11
View File
@@ -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})
Regular → Executable
+6 -10
View File
@@ -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
+132
View File
@@ -0,0 +1,132 @@
/*
* ebusd - daemon for communication with eBUS heating systems.
* Copyright (C) 2018 John Baier <ebusd@ebusd.eu>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "lib/utils/httpclient.h"
#include <cstring>
#include <cstdlib>
#include <sstream>
#include <ios>
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<unsigned>(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<unsigned>(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
+111
View File
@@ -0,0 +1,111 @@
/*
* ebusd - daemon for communication with eBUS heating systems.
* Copyright (C) 2018 John Baier <ebusd@ebusd.eu>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef LIB_UTILS_HTTP_H_
#define LIB_UTILS_HTTP_H_
#include <unistd.h>
#include <cstdint>
#include <string>
#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_