mirror of
https://github.com/Icinga/icinga2.git
synced 2025-07-22 21:24:41 +02:00
Merge pull request #7196 from Icinga/feature/network-cleanup
Cleanup old code (HTTP, Cluster)
This commit is contained in:
commit
99bb7fa99c
@ -61,7 +61,6 @@ set(base_SOURCES
|
|||||||
serializer.cpp serializer.hpp
|
serializer.cpp serializer.hpp
|
||||||
singleton.hpp
|
singleton.hpp
|
||||||
socket.cpp socket.hpp
|
socket.cpp socket.hpp
|
||||||
socketevents.cpp socketevents-epoll.cpp socketevents-poll.cpp socketevents.hpp
|
|
||||||
stacktrace.cpp stacktrace.hpp
|
stacktrace.cpp stacktrace.hpp
|
||||||
statsfunction.hpp
|
statsfunction.hpp
|
||||||
stdiostream.cpp stdiostream.hpp
|
stdiostream.cpp stdiostream.hpp
|
||||||
|
@ -11,7 +11,7 @@ namespace icinga
|
|||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A network stream.
|
* A network stream. DEPRECATED - Use Boost ASIO instead.
|
||||||
*
|
*
|
||||||
* @ingroup base
|
* @ingroup base
|
||||||
*/
|
*/
|
||||||
|
@ -1,189 +0,0 @@
|
|||||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
|
||||||
|
|
||||||
#include "base/socketevents.hpp"
|
|
||||||
#include "base/exception.hpp"
|
|
||||||
#include "base/logger.hpp"
|
|
||||||
#include "base/utility.hpp"
|
|
||||||
#include <boost/thread/once.hpp>
|
|
||||||
#include <map>
|
|
||||||
#ifdef __linux__
|
|
||||||
# include <sys/epoll.h>
|
|
||||||
|
|
||||||
using namespace icinga;
|
|
||||||
|
|
||||||
void SocketEventEngineEpoll::InitializeThread(int tid)
|
|
||||||
{
|
|
||||||
m_PollFDs[tid] = epoll_create(128);
|
|
||||||
Utility::SetCloExec(m_PollFDs[tid]);
|
|
||||||
|
|
||||||
SocketEventDescriptor sed;
|
|
||||||
|
|
||||||
m_Sockets[tid][m_EventFDs[tid][0]] = sed;
|
|
||||||
m_FDChanged[tid] = true;
|
|
||||||
|
|
||||||
epoll_event event;
|
|
||||||
memset(&event, 0, sizeof(event));
|
|
||||||
event.data.fd = m_EventFDs[tid][0];
|
|
||||||
event.events = EPOLLIN;
|
|
||||||
epoll_ctl(m_PollFDs[tid], EPOLL_CTL_ADD, m_EventFDs[tid][0], &event);
|
|
||||||
}
|
|
||||||
|
|
||||||
int SocketEventEngineEpoll::PollToEpoll(int events)
|
|
||||||
{
|
|
||||||
int result = 0;
|
|
||||||
|
|
||||||
if (events & POLLIN)
|
|
||||||
result |= EPOLLIN;
|
|
||||||
|
|
||||||
if (events & POLLOUT)
|
|
||||||
result |= EPOLLOUT;
|
|
||||||
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SocketEventEngineEpoll::EpollToPoll(int events)
|
|
||||||
{
|
|
||||||
int result = 0;
|
|
||||||
|
|
||||||
if (events & EPOLLIN)
|
|
||||||
result |= POLLIN;
|
|
||||||
|
|
||||||
if (events & EPOLLOUT)
|
|
||||||
result |= POLLOUT;
|
|
||||||
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketEventEngineEpoll::ThreadProc(int tid)
|
|
||||||
{
|
|
||||||
Utility::SetThreadName("SocketIO");
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock lock(m_EventMutex[tid]);
|
|
||||||
|
|
||||||
if (m_FDChanged[tid]) {
|
|
||||||
m_FDChanged[tid] = false;
|
|
||||||
m_CV[tid].notify_all();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
epoll_event pevents[64];
|
|
||||||
int ready = epoll_wait(m_PollFDs[tid], pevents, sizeof(pevents) / sizeof(pevents[0]), -1);
|
|
||||||
|
|
||||||
std::vector<EventDescription> events;
|
|
||||||
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock lock(m_EventMutex[tid]);
|
|
||||||
|
|
||||||
if (m_FDChanged[tid]) {
|
|
||||||
m_FDChanged[tid] = false;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < ready; i++) {
|
|
||||||
if (pevents[i].data.fd == m_EventFDs[tid][0]) {
|
|
||||||
char buffer[512];
|
|
||||||
if (recv(m_EventFDs[tid][0], buffer, sizeof(buffer), 0) < 0)
|
|
||||||
Log(LogCritical, "SocketEvents", "Read from event FD failed.");
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((pevents[i].events & (EPOLLIN | EPOLLOUT | EPOLLHUP | EPOLLERR)) == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
EventDescription event;
|
|
||||||
event.REvents = SocketEventEngineEpoll::EpollToPoll(pevents[i].events);
|
|
||||||
event.Descriptor = m_Sockets[tid][pevents[i].data.fd];
|
|
||||||
|
|
||||||
events.emplace_back(std::move(event));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const EventDescription& event : events) {
|
|
||||||
try {
|
|
||||||
event.Descriptor.EventInterface->OnEvent(event.REvents);
|
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
Log(LogCritical, "SocketEvents")
|
|
||||||
<< "Exception thrown in socket I/O handler:\n"
|
|
||||||
<< DiagnosticInformation(ex);
|
|
||||||
} catch (...) {
|
|
||||||
Log(LogCritical, "SocketEvents", "Exception of unknown type thrown in socket I/O handler.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketEventEngineEpoll::Register(SocketEvents *se)
|
|
||||||
{
|
|
||||||
int tid = se->m_ID % SOCKET_IOTHREADS;
|
|
||||||
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock lock(m_EventMutex[tid]);
|
|
||||||
|
|
||||||
VERIFY(se->m_FD != INVALID_SOCKET);
|
|
||||||
|
|
||||||
SocketEventDescriptor desc;
|
|
||||||
desc.EventInterface = se;
|
|
||||||
|
|
||||||
VERIFY(m_Sockets[tid].find(se->m_FD) == m_Sockets[tid].end());
|
|
||||||
|
|
||||||
m_Sockets[tid][se->m_FD] = desc;
|
|
||||||
|
|
||||||
epoll_event event;
|
|
||||||
memset(&event, 0, sizeof(event));
|
|
||||||
event.data.fd = se->m_FD;
|
|
||||||
event.events = 0;
|
|
||||||
epoll_ctl(m_PollFDs[tid], EPOLL_CTL_ADD, se->m_FD, &event);
|
|
||||||
|
|
||||||
se->m_Events = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketEventEngineEpoll::Unregister(SocketEvents *se)
|
|
||||||
{
|
|
||||||
int tid = se->m_ID % SOCKET_IOTHREADS;
|
|
||||||
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock lock(m_EventMutex[tid]);
|
|
||||||
|
|
||||||
if (se->m_FD == INVALID_SOCKET)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_Sockets[tid].erase(se->m_FD);
|
|
||||||
m_FDChanged[tid] = true;
|
|
||||||
|
|
||||||
epoll_ctl(m_PollFDs[tid], EPOLL_CTL_DEL, se->m_FD, nullptr);
|
|
||||||
|
|
||||||
se->m_FD = INVALID_SOCKET;
|
|
||||||
se->m_Events = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
WakeUpThread(tid, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketEventEngineEpoll::ChangeEvents(SocketEvents *se, int events)
|
|
||||||
{
|
|
||||||
if (se->m_FD == INVALID_SOCKET)
|
|
||||||
BOOST_THROW_EXCEPTION(std::runtime_error("Tried to read/write from a closed socket."));
|
|
||||||
|
|
||||||
int tid = se->m_ID % SOCKET_IOTHREADS;
|
|
||||||
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock lock(m_EventMutex[tid]);
|
|
||||||
|
|
||||||
auto it = m_Sockets[tid].find(se->m_FD);
|
|
||||||
|
|
||||||
if (it == m_Sockets[tid].end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
epoll_event event;
|
|
||||||
memset(&event, 0, sizeof(event));
|
|
||||||
event.data.fd = se->m_FD;
|
|
||||||
event.events = SocketEventEngineEpoll::PollToEpoll(events);
|
|
||||||
epoll_ctl(m_PollFDs[tid], EPOLL_CTL_MOD, se->m_FD, &event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif /* __linux__ */
|
|
@ -1,190 +0,0 @@
|
|||||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
|
||||||
|
|
||||||
#include "base/socketevents.hpp"
|
|
||||||
#include "base/exception.hpp"
|
|
||||||
#include "base/logger.hpp"
|
|
||||||
#include "base/utility.hpp"
|
|
||||||
#include <boost/thread/once.hpp>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
using namespace icinga;
|
|
||||||
|
|
||||||
void SocketEventEnginePoll::InitializeThread(int tid)
|
|
||||||
{
|
|
||||||
SocketEventDescriptor sed;
|
|
||||||
sed.Events = POLLIN;
|
|
||||||
|
|
||||||
m_Sockets[tid][m_EventFDs[tid][0]] = sed;
|
|
||||||
m_FDChanged[tid] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketEventEnginePoll::ThreadProc(int tid)
|
|
||||||
{
|
|
||||||
Utility::SetThreadName("SocketIO");
|
|
||||||
|
|
||||||
std::vector<pollfd> pfds;
|
|
||||||
std::vector<SocketEventDescriptor> descriptors;
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock lock(m_EventMutex[tid]);
|
|
||||||
|
|
||||||
if (m_FDChanged[tid]) {
|
|
||||||
pfds.resize(m_Sockets[tid].size());
|
|
||||||
descriptors.resize(m_Sockets[tid].size());
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
typedef std::map<SOCKET, SocketEventDescriptor>::value_type kv_pair;
|
|
||||||
|
|
||||||
for (const kv_pair& desc : m_Sockets[tid]) {
|
|
||||||
if (desc.second.Events == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
int events = desc.second.Events;
|
|
||||||
|
|
||||||
if (desc.second.EventInterface) {
|
|
||||||
desc.second.EventInterface->m_EnginePrivate = &pfds[i];
|
|
||||||
|
|
||||||
if (!desc.second.EventInterface->m_Events)
|
|
||||||
events = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pfds[i].fd = desc.first;
|
|
||||||
pfds[i].events = events;
|
|
||||||
descriptors[i] = desc.second;
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
pfds.resize(i);
|
|
||||||
|
|
||||||
m_FDChanged[tid] = false;
|
|
||||||
m_CV[tid].notify_all();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT(!pfds.empty());
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
(void) WSAPoll(&pfds[0], pfds.size(), -1);
|
|
||||||
#else /* _WIN32 */
|
|
||||||
(void) poll(&pfds[0], pfds.size(), -1);
|
|
||||||
#endif /* _WIN32 */
|
|
||||||
|
|
||||||
std::vector<EventDescription> events;
|
|
||||||
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock lock(m_EventMutex[tid]);
|
|
||||||
|
|
||||||
if (m_FDChanged[tid])
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (std::vector<pollfd>::size_type i = 0; i < pfds.size(); i++) {
|
|
||||||
if ((pfds[i].revents & (POLLIN | POLLOUT | POLLHUP | POLLERR)) == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (pfds[i].fd == m_EventFDs[tid][0]) {
|
|
||||||
char buffer[512];
|
|
||||||
if (recv(m_EventFDs[tid][0], buffer, sizeof(buffer), 0) < 0)
|
|
||||||
Log(LogCritical, "SocketEvents", "Read from event FD failed.");
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
EventDescription event;
|
|
||||||
event.REvents = pfds[i].revents;
|
|
||||||
event.Descriptor = descriptors[i];
|
|
||||||
|
|
||||||
events.emplace_back(std::move(event));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const EventDescription& event : events) {
|
|
||||||
try {
|
|
||||||
event.Descriptor.EventInterface->OnEvent(event.REvents);
|
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
Log(LogCritical, "SocketEvents")
|
|
||||||
<< "Exception thrown in socket I/O handler:\n"
|
|
||||||
<< DiagnosticInformation(ex);
|
|
||||||
} catch (...) {
|
|
||||||
Log(LogCritical, "SocketEvents", "Exception of unknown type thrown in socket I/O handler.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketEventEnginePoll::Register(SocketEvents *se)
|
|
||||||
{
|
|
||||||
int tid = se->m_ID % SOCKET_IOTHREADS;
|
|
||||||
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock lock(m_EventMutex[tid]);
|
|
||||||
|
|
||||||
VERIFY(se->m_FD != INVALID_SOCKET);
|
|
||||||
|
|
||||||
SocketEventDescriptor desc;
|
|
||||||
desc.Events = 0;
|
|
||||||
desc.EventInterface = se;
|
|
||||||
|
|
||||||
VERIFY(m_Sockets[tid].find(se->m_FD) == m_Sockets[tid].end());
|
|
||||||
|
|
||||||
m_Sockets[tid][se->m_FD] = desc;
|
|
||||||
|
|
||||||
m_FDChanged[tid] = true;
|
|
||||||
|
|
||||||
se->m_Events = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
WakeUpThread(tid, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketEventEnginePoll::Unregister(SocketEvents *se)
|
|
||||||
{
|
|
||||||
int tid = se->m_ID % SOCKET_IOTHREADS;
|
|
||||||
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock lock(m_EventMutex[tid]);
|
|
||||||
|
|
||||||
if (se->m_FD == INVALID_SOCKET)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_Sockets[tid].erase(se->m_FD);
|
|
||||||
m_FDChanged[tid] = true;
|
|
||||||
|
|
||||||
se->m_FD = INVALID_SOCKET;
|
|
||||||
se->m_Events = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
WakeUpThread(tid, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketEventEnginePoll::ChangeEvents(SocketEvents *se, int events)
|
|
||||||
{
|
|
||||||
if (se->m_FD == INVALID_SOCKET)
|
|
||||||
BOOST_THROW_EXCEPTION(std::runtime_error("Tried to read/write from a closed socket."));
|
|
||||||
|
|
||||||
int tid = se->m_ID % SOCKET_IOTHREADS;
|
|
||||||
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock lock(m_EventMutex[tid]);
|
|
||||||
|
|
||||||
auto it = m_Sockets[tid].find(se->m_FD);
|
|
||||||
|
|
||||||
if (it == m_Sockets[tid].end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (it->second.Events == events)
|
|
||||||
return;
|
|
||||||
|
|
||||||
it->second.Events = events;
|
|
||||||
|
|
||||||
if (se->m_EnginePrivate && std::this_thread::get_id() == m_Threads[tid].get_id())
|
|
||||||
((pollfd *)se->m_EnginePrivate)->events = events;
|
|
||||||
else
|
|
||||||
m_FDChanged[tid] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
WakeUpThread(tid, false);
|
|
||||||
}
|
|
||||||
|
|
@ -1,142 +0,0 @@
|
|||||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
|
||||||
|
|
||||||
#include "base/socketevents.hpp"
|
|
||||||
#include "base/exception.hpp"
|
|
||||||
#include "base/logger.hpp"
|
|
||||||
#include "base/application.hpp"
|
|
||||||
#include "base/scriptglobal.hpp"
|
|
||||||
#include "base/utility.hpp"
|
|
||||||
#include <boost/thread/once.hpp>
|
|
||||||
#include <map>
|
|
||||||
#ifdef __linux__
|
|
||||||
# include <sys/epoll.h>
|
|
||||||
#endif /* __linux__ */
|
|
||||||
|
|
||||||
using namespace icinga;
|
|
||||||
|
|
||||||
static boost::once_flag l_SocketIOOnceFlag = BOOST_ONCE_INIT;
|
|
||||||
static SocketEventEngine *l_SocketIOEngine;
|
|
||||||
|
|
||||||
int SocketEvents::m_NextID = 0;
|
|
||||||
|
|
||||||
void SocketEventEngine::Start()
|
|
||||||
{
|
|
||||||
for (int tid = 0; tid < SOCKET_IOTHREADS; tid++) {
|
|
||||||
Socket::SocketPair(m_EventFDs[tid]);
|
|
||||||
|
|
||||||
Utility::SetNonBlockingSocket(m_EventFDs[tid][0]);
|
|
||||||
Utility::SetNonBlockingSocket(m_EventFDs[tid][1]);
|
|
||||||
|
|
||||||
#ifndef _WIN32
|
|
||||||
Utility::SetCloExec(m_EventFDs[tid][0]);
|
|
||||||
Utility::SetCloExec(m_EventFDs[tid][1]);
|
|
||||||
#endif /* _WIN32 */
|
|
||||||
|
|
||||||
InitializeThread(tid);
|
|
||||||
|
|
||||||
m_Threads[tid] = std::thread(std::bind(&SocketEventEngine::ThreadProc, this, tid));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketEventEngine::WakeUpThread(int sid, bool wait)
|
|
||||||
{
|
|
||||||
int tid = sid % SOCKET_IOTHREADS;
|
|
||||||
|
|
||||||
if (std::this_thread::get_id() == m_Threads[tid].get_id())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (wait) {
|
|
||||||
boost::mutex::scoped_lock lock(m_EventMutex[tid]);
|
|
||||||
|
|
||||||
m_FDChanged[tid] = true;
|
|
||||||
|
|
||||||
while (m_FDChanged[tid]) {
|
|
||||||
(void) send(m_EventFDs[tid][1], "T", 1, 0);
|
|
||||||
|
|
||||||
boost::system_time const timeout = boost::get_system_time() + boost::posix_time::milliseconds(50);
|
|
||||||
m_CV[tid].timed_wait(lock, timeout);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(void) send(m_EventFDs[tid][1], "T", 1, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketEvents::InitializeEngine()
|
|
||||||
{
|
|
||||||
String eventEngine = Configuration::EventEngine;
|
|
||||||
|
|
||||||
if (eventEngine.IsEmpty())
|
|
||||||
#ifdef __linux__
|
|
||||||
eventEngine = "epoll";
|
|
||||||
#else /* __linux__ */
|
|
||||||
eventEngine = "poll";
|
|
||||||
#endif /* __linux__ */
|
|
||||||
|
|
||||||
if (eventEngine == "poll")
|
|
||||||
l_SocketIOEngine = new SocketEventEnginePoll();
|
|
||||||
#ifdef __linux__
|
|
||||||
else if (eventEngine == "epoll")
|
|
||||||
l_SocketIOEngine = new SocketEventEngineEpoll();
|
|
||||||
#endif /* __linux__ */
|
|
||||||
else {
|
|
||||||
Log(LogWarning, "SocketEvents")
|
|
||||||
<< "Invalid event engine selected: " << eventEngine << " - Falling back to 'poll'";
|
|
||||||
|
|
||||||
eventEngine = "poll";
|
|
||||||
|
|
||||||
l_SocketIOEngine = new SocketEventEnginePoll();
|
|
||||||
}
|
|
||||||
|
|
||||||
l_SocketIOEngine->Start();
|
|
||||||
|
|
||||||
Configuration::EventEngine = eventEngine;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for the SocketEvents class.
|
|
||||||
*/
|
|
||||||
SocketEvents::SocketEvents(const Socket::Ptr& socket)
|
|
||||||
: m_ID(m_NextID++), m_FD(socket->GetFD()), m_EnginePrivate(nullptr)
|
|
||||||
{
|
|
||||||
boost::call_once(l_SocketIOOnceFlag, &SocketEvents::InitializeEngine);
|
|
||||||
|
|
||||||
Register();
|
|
||||||
}
|
|
||||||
|
|
||||||
SocketEvents::~SocketEvents()
|
|
||||||
{
|
|
||||||
VERIFY(m_FD == INVALID_SOCKET);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketEvents::Register()
|
|
||||||
{
|
|
||||||
l_SocketIOEngine->Register(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketEvents::Unregister()
|
|
||||||
{
|
|
||||||
l_SocketIOEngine->Unregister(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketEvents::ChangeEvents(int events)
|
|
||||||
{
|
|
||||||
l_SocketIOEngine->ChangeEvents(this, events);
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::mutex& SocketEventEngine::GetMutex(int tid)
|
|
||||||
{
|
|
||||||
return m_EventMutex[tid];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SocketEvents::IsHandlingEvents() const
|
|
||||||
{
|
|
||||||
int tid = m_ID % SOCKET_IOTHREADS;
|
|
||||||
boost::mutex::scoped_lock lock(l_SocketIOEngine->GetMutex(tid));
|
|
||||||
return m_Events;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketEvents::OnEvent(int revents)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,137 +0,0 @@
|
|||||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
|
||||||
|
|
||||||
#ifndef SOCKETEVENTS_H
|
|
||||||
#define SOCKETEVENTS_H
|
|
||||||
|
|
||||||
#include "base/i2-base.hpp"
|
|
||||||
#include "base/socket.hpp"
|
|
||||||
#include "base/stream.hpp"
|
|
||||||
#include <boost/thread/condition_variable.hpp>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#ifndef _WIN32
|
|
||||||
# include <poll.h>
|
|
||||||
#endif /* _WIN32 */
|
|
||||||
|
|
||||||
namespace icinga
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Socket event interface
|
|
||||||
*
|
|
||||||
* @ingroup base
|
|
||||||
*/
|
|
||||||
class SocketEvents : public Stream
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DECLARE_PTR_TYPEDEFS(SocketEvents);
|
|
||||||
|
|
||||||
~SocketEvents();
|
|
||||||
|
|
||||||
virtual void OnEvent(int revents);
|
|
||||||
|
|
||||||
void Unregister();
|
|
||||||
|
|
||||||
void ChangeEvents(int events);
|
|
||||||
|
|
||||||
bool IsHandlingEvents() const;
|
|
||||||
|
|
||||||
void *GetEnginePrivate() const;
|
|
||||||
void SetEnginePrivate(void *priv);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
SocketEvents(const Socket::Ptr& socket);
|
|
||||||
|
|
||||||
private:
|
|
||||||
int m_ID;
|
|
||||||
SOCKET m_FD;
|
|
||||||
bool m_Events;
|
|
||||||
void *m_EnginePrivate;
|
|
||||||
|
|
||||||
static int m_NextID;
|
|
||||||
|
|
||||||
static void InitializeEngine();
|
|
||||||
|
|
||||||
void WakeUpThread(bool wait = false);
|
|
||||||
|
|
||||||
void Register();
|
|
||||||
|
|
||||||
friend class SocketEventEnginePoll;
|
|
||||||
friend class SocketEventEngineEpoll;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define SOCKET_IOTHREADS 8
|
|
||||||
|
|
||||||
struct SocketEventDescriptor
|
|
||||||
{
|
|
||||||
int Events{POLLIN};
|
|
||||||
SocketEvents::Ptr EventInterface;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct EventDescription
|
|
||||||
{
|
|
||||||
int REvents;
|
|
||||||
SocketEventDescriptor Descriptor;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SocketEventEngine
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void Start();
|
|
||||||
|
|
||||||
void WakeUpThread(int sid, bool wait);
|
|
||||||
|
|
||||||
boost::mutex& GetMutex(int tid);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void InitializeThread(int tid) = 0;
|
|
||||||
virtual void ThreadProc(int tid) = 0;
|
|
||||||
virtual void Register(SocketEvents *se) = 0;
|
|
||||||
virtual void Unregister(SocketEvents *se) = 0;
|
|
||||||
virtual void ChangeEvents(SocketEvents *se, int events) = 0;
|
|
||||||
|
|
||||||
std::thread m_Threads[SOCKET_IOTHREADS];
|
|
||||||
SOCKET m_EventFDs[SOCKET_IOTHREADS][2];
|
|
||||||
bool m_FDChanged[SOCKET_IOTHREADS];
|
|
||||||
boost::mutex m_EventMutex[SOCKET_IOTHREADS];
|
|
||||||
boost::condition_variable m_CV[SOCKET_IOTHREADS];
|
|
||||||
std::map<SOCKET, SocketEventDescriptor> m_Sockets[SOCKET_IOTHREADS];
|
|
||||||
|
|
||||||
friend class SocketEvents;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SocketEventEnginePoll final : public SocketEventEngine
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void Register(SocketEvents *se) override;
|
|
||||||
void Unregister(SocketEvents *se) override;
|
|
||||||
void ChangeEvents(SocketEvents *se, int events) override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void InitializeThread(int tid) override;
|
|
||||||
void ThreadProc(int tid) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
class SocketEventEngineEpoll : public SocketEventEngine
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual void Register(SocketEvents *se);
|
|
||||||
virtual void Unregister(SocketEvents *se);
|
|
||||||
virtual void ChangeEvents(SocketEvents *se, int events);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void InitializeThread(int tid);
|
|
||||||
virtual void ThreadProc(int tid);
|
|
||||||
|
|
||||||
private:
|
|
||||||
SOCKET m_PollFDs[SOCKET_IOTHREADS];
|
|
||||||
|
|
||||||
static int PollToEpoll(int events);
|
|
||||||
static int EpollToPoll(int events);
|
|
||||||
};
|
|
||||||
#endif /* __linux__ */
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* SOCKETEVENTS_H */
|
|
@ -12,7 +12,7 @@ namespace icinga
|
|||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A TCP socket.
|
* A TCP socket. DEPRECATED - Use Boost ASIO instead.
|
||||||
*
|
*
|
||||||
* @ingroup base
|
* @ingroup base
|
||||||
*/
|
*/
|
||||||
@ -27,6 +27,11 @@ public:
|
|||||||
void Connect(const String& node, const String& service);
|
void Connect(const String& node, const String& service);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TCP Connect based on Boost ASIO.
|
||||||
|
*
|
||||||
|
* @ingroup base
|
||||||
|
*/
|
||||||
template<class Socket>
|
template<class Socket>
|
||||||
void Connect(Socket& socket, const String& node, const String& service)
|
void Connect(Socket& socket, const String& node, const String& service)
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
||||||
|
|
||||||
#include "base/application.hpp"
|
|
||||||
#include "base/tlsstream.hpp"
|
#include "base/tlsstream.hpp"
|
||||||
|
#include "base/application.hpp"
|
||||||
#include "base/utility.hpp"
|
#include "base/utility.hpp"
|
||||||
#include "base/exception.hpp"
|
#include "base/exception.hpp"
|
||||||
#include "base/logger.hpp"
|
#include "base/logger.hpp"
|
||||||
@ -16,446 +16,8 @@
|
|||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#ifndef _WIN32
|
|
||||||
# include <poll.h>
|
|
||||||
#endif /* _WIN32 */
|
|
||||||
|
|
||||||
#define TLS_TIMEOUT_SECONDS 10
|
|
||||||
|
|
||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
|
|
||||||
int TlsStream::m_SSLIndex;
|
|
||||||
bool TlsStream::m_SSLIndexInitialized = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for the TlsStream class.
|
|
||||||
*
|
|
||||||
* @param role The role of the client.
|
|
||||||
* @param sslContext The SSL context for the client.
|
|
||||||
*/
|
|
||||||
TlsStream::TlsStream(const Socket::Ptr& socket, const String& hostname, ConnectionRole role, const std::shared_ptr<SSL_CTX>& sslContext)
|
|
||||||
: TlsStream(socket, hostname, role, sslContext.get())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for the TlsStream class.
|
|
||||||
*
|
|
||||||
* @param role The role of the client.
|
|
||||||
* @param sslContext The SSL context for the client.
|
|
||||||
*/
|
|
||||||
TlsStream::TlsStream(const Socket::Ptr& socket, const String& hostname, ConnectionRole role, const std::shared_ptr<boost::asio::ssl::context>& sslContext)
|
|
||||||
: TlsStream(socket, hostname, role, sslContext->native_handle())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for the TlsStream class.
|
|
||||||
*
|
|
||||||
* @param role The role of the client.
|
|
||||||
* @param sslContext The SSL context for the client.
|
|
||||||
*/
|
|
||||||
TlsStream::TlsStream(const Socket::Ptr& socket, const String& hostname, ConnectionRole role, SSL_CTX* sslContext)
|
|
||||||
: SocketEvents(socket), m_Eof(false), m_HandshakeOK(false), m_VerifyOK(true), m_ErrorCode(0),
|
|
||||||
m_ErrorOccurred(false), m_Socket(socket), m_Role(role), m_SendQ(new FIFO()), m_RecvQ(new FIFO()),
|
|
||||||
m_CurrentAction(TlsActionNone), m_Retry(false), m_Shutdown(false)
|
|
||||||
{
|
|
||||||
std::ostringstream msgbuf;
|
|
||||||
char errbuf[256];
|
|
||||||
|
|
||||||
m_SSL = std::shared_ptr<SSL>(SSL_new(sslContext), SSL_free);
|
|
||||||
|
|
||||||
if (!m_SSL) {
|
|
||||||
msgbuf << "SSL_new() failed with code " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
|
|
||||||
Log(LogCritical, "TlsStream", msgbuf.str());
|
|
||||||
|
|
||||||
BOOST_THROW_EXCEPTION(openssl_error()
|
|
||||||
<< boost::errinfo_api_function("SSL_new")
|
|
||||||
<< errinfo_openssl_error(ERR_peek_error()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_SSLIndexInitialized) {
|
|
||||||
m_SSLIndex = SSL_get_ex_new_index(0, const_cast<char *>("TlsStream"), nullptr, nullptr, nullptr);
|
|
||||||
m_SSLIndexInitialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
SSL_set_ex_data(m_SSL.get(), m_SSLIndex, this);
|
|
||||||
|
|
||||||
SSL_set_verify(m_SSL.get(), SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, &TlsStream::ValidateCertificate);
|
|
||||||
|
|
||||||
socket->MakeNonBlocking();
|
|
||||||
|
|
||||||
SSL_set_fd(m_SSL.get(), socket->GetFD());
|
|
||||||
|
|
||||||
if (m_Role == RoleServer)
|
|
||||||
SSL_set_accept_state(m_SSL.get());
|
|
||||||
else {
|
|
||||||
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
|
|
||||||
if (!hostname.IsEmpty())
|
|
||||||
SSL_set_tlsext_host_name(m_SSL.get(), hostname.CStr());
|
|
||||||
#endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */
|
|
||||||
|
|
||||||
SSL_set_connect_state(m_SSL.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TlsStream::~TlsStream()
|
|
||||||
{
|
|
||||||
CloseInternal(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
int TlsStream::ValidateCertificate(int preverify_ok, X509_STORE_CTX *ctx)
|
|
||||||
{
|
|
||||||
auto *ssl = static_cast<SSL *>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
|
|
||||||
auto *stream = static_cast<TlsStream *>(SSL_get_ex_data(ssl, m_SSLIndex));
|
|
||||||
|
|
||||||
if (!preverify_ok) {
|
|
||||||
stream->m_VerifyOK = false;
|
|
||||||
|
|
||||||
std::ostringstream msgbuf;
|
|
||||||
int err = X509_STORE_CTX_get_error(ctx);
|
|
||||||
msgbuf << "code " << err << ": " << X509_verify_cert_error_string(err);
|
|
||||||
stream->m_VerifyError = msgbuf.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TlsStream::IsVerifyOK() const
|
|
||||||
{
|
|
||||||
return m_VerifyOK;
|
|
||||||
}
|
|
||||||
|
|
||||||
String TlsStream::GetVerifyError() const
|
|
||||||
{
|
|
||||||
return m_VerifyError;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the X509 certficate for this client.
|
|
||||||
*
|
|
||||||
* @returns The X509 certificate.
|
|
||||||
*/
|
|
||||||
std::shared_ptr<X509> TlsStream::GetClientCertificate() const
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock lock(m_Mutex);
|
|
||||||
return std::shared_ptr<X509>(SSL_get_certificate(m_SSL.get()), &Utility::NullDeleter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the X509 certficate for the peer.
|
|
||||||
*
|
|
||||||
* @returns The X509 certificate.
|
|
||||||
*/
|
|
||||||
std::shared_ptr<X509> TlsStream::GetPeerCertificate() const
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock lock(m_Mutex);
|
|
||||||
return std::shared_ptr<X509>(SSL_get_peer_certificate(m_SSL.get()), X509_free);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TlsStream::OnEvent(int revents)
|
|
||||||
{
|
|
||||||
int rc;
|
|
||||||
size_t count;
|
|
||||||
|
|
||||||
boost::mutex::scoped_lock lock(m_Mutex);
|
|
||||||
|
|
||||||
if (!m_SSL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
char buffer[64 * 1024];
|
|
||||||
|
|
||||||
if (m_CurrentAction == TlsActionNone) {
|
|
||||||
if (revents & (POLLIN | POLLERR | POLLHUP))
|
|
||||||
m_CurrentAction = TlsActionRead;
|
|
||||||
else if (m_SendQ->GetAvailableBytes() > 0 && (revents & POLLOUT))
|
|
||||||
m_CurrentAction = TlsActionWrite;
|
|
||||||
else {
|
|
||||||
ChangeEvents(POLLIN);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool success = false;
|
|
||||||
|
|
||||||
/* Clear error queue for this thread before using SSL_{read,write,do_handshake}.
|
|
||||||
* Otherwise SSL_*_error() does not work reliably.
|
|
||||||
*/
|
|
||||||
ERR_clear_error();
|
|
||||||
|
|
||||||
size_t readTotal = 0;
|
|
||||||
|
|
||||||
switch (m_CurrentAction) {
|
|
||||||
case TlsActionRead:
|
|
||||||
do {
|
|
||||||
rc = SSL_read(m_SSL.get(), buffer, sizeof(buffer));
|
|
||||||
|
|
||||||
if (rc > 0) {
|
|
||||||
m_RecvQ->Write(buffer, rc);
|
|
||||||
success = true;
|
|
||||||
|
|
||||||
readTotal += rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef I2_DEBUG /* I2_DEBUG */
|
|
||||||
Log(LogDebug, "TlsStream")
|
|
||||||
<< "Read bytes: " << rc << " Total read bytes: " << readTotal;
|
|
||||||
#endif /* I2_DEBUG */
|
|
||||||
/* Limit read size. We cannot do this check inside the while loop
|
|
||||||
* since below should solely check whether OpenSSL has more data
|
|
||||||
* or not. */
|
|
||||||
if (readTotal >= 64 * 1024) {
|
|
||||||
#ifdef I2_DEBUG /* I2_DEBUG */
|
|
||||||
Log(LogWarning, "TlsStream")
|
|
||||||
<< "Maximum read bytes exceeded: " << readTotal;
|
|
||||||
#endif /* I2_DEBUG */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Use OpenSSL's state machine here to determine whether we need
|
|
||||||
* to read more data. SSL_has_pending() is available with 1.1.0.
|
|
||||||
*/
|
|
||||||
} while (SSL_pending(m_SSL.get()));
|
|
||||||
|
|
||||||
if (success)
|
|
||||||
m_CV.notify_all();
|
|
||||||
|
|
||||||
break;
|
|
||||||
case TlsActionWrite:
|
|
||||||
count = m_SendQ->Peek(buffer, sizeof(buffer), true);
|
|
||||||
|
|
||||||
rc = SSL_write(m_SSL.get(), buffer, count);
|
|
||||||
|
|
||||||
if (rc > 0) {
|
|
||||||
m_SendQ->Read(nullptr, rc, true);
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case TlsActionHandshake:
|
|
||||||
rc = SSL_do_handshake(m_SSL.get());
|
|
||||||
|
|
||||||
if (rc > 0) {
|
|
||||||
success = true;
|
|
||||||
m_HandshakeOK = true;
|
|
||||||
m_CV.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
VERIFY(!"Invalid TlsAction");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rc <= 0) {
|
|
||||||
int err = SSL_get_error(m_SSL.get(), rc);
|
|
||||||
|
|
||||||
switch (err) {
|
|
||||||
case SSL_ERROR_WANT_READ:
|
|
||||||
m_Retry = true;
|
|
||||||
ChangeEvents(POLLIN);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case SSL_ERROR_WANT_WRITE:
|
|
||||||
m_Retry = true;
|
|
||||||
ChangeEvents(POLLOUT);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case SSL_ERROR_ZERO_RETURN:
|
|
||||||
lock.unlock();
|
|
||||||
|
|
||||||
Close();
|
|
||||||
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
m_ErrorCode = ERR_peek_error();
|
|
||||||
m_ErrorOccurred = true;
|
|
||||||
|
|
||||||
if (m_ErrorCode != 0) {
|
|
||||||
char errbuf[256];
|
|
||||||
Log(LogWarning, "TlsStream")
|
|
||||||
<< "OpenSSL error: " << ERR_error_string(m_ErrorCode, errbuf);
|
|
||||||
} else {
|
|
||||||
Log(LogWarning, "TlsStream", "TLS stream was disconnected.");
|
|
||||||
}
|
|
||||||
|
|
||||||
lock.unlock();
|
|
||||||
|
|
||||||
Close();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
m_CurrentAction = TlsActionNone;
|
|
||||||
|
|
||||||
if (!m_Eof) {
|
|
||||||
if (m_SendQ->GetAvailableBytes() > 0)
|
|
||||||
ChangeEvents(POLLIN|POLLOUT);
|
|
||||||
else
|
|
||||||
ChangeEvents(POLLIN);
|
|
||||||
}
|
|
||||||
|
|
||||||
lock.unlock();
|
|
||||||
|
|
||||||
while (m_RecvQ->IsDataAvailable() && IsHandlingEvents())
|
|
||||||
SignalDataAvailable();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_Shutdown && !m_SendQ->IsDataAvailable()) {
|
|
||||||
if (!success)
|
|
||||||
lock.unlock();
|
|
||||||
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TlsStream::HandleError() const
|
|
||||||
{
|
|
||||||
if (m_ErrorOccurred) {
|
|
||||||
BOOST_THROW_EXCEPTION(openssl_error()
|
|
||||||
<< boost::errinfo_api_function("TlsStream::OnEvent")
|
|
||||||
<< errinfo_openssl_error(m_ErrorCode));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TlsStream::Handshake()
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock lock(m_Mutex);
|
|
||||||
|
|
||||||
m_CurrentAction = TlsActionHandshake;
|
|
||||||
ChangeEvents(POLLOUT);
|
|
||||||
|
|
||||||
boost::system_time const timeout = boost::get_system_time() + boost::posix_time::milliseconds(long(Configuration::TlsHandshakeTimeout * 1000));
|
|
||||||
|
|
||||||
while (!m_HandshakeOK && !m_ErrorOccurred && !m_Eof && timeout > boost::get_system_time())
|
|
||||||
m_CV.timed_wait(lock, timeout);
|
|
||||||
|
|
||||||
if (timeout < boost::get_system_time())
|
|
||||||
BOOST_THROW_EXCEPTION(std::runtime_error("Timeout was reached (" + Convert::ToString(Configuration::TlsHandshakeTimeout) + ") during TLS handshake."));
|
|
||||||
|
|
||||||
if (m_Eof)
|
|
||||||
BOOST_THROW_EXCEPTION(std::runtime_error("Socket was closed during TLS handshake."));
|
|
||||||
|
|
||||||
HandleError();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes data for the stream.
|
|
||||||
*/
|
|
||||||
size_t TlsStream::Peek(void *buffer, size_t count, bool allow_partial)
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock lock(m_Mutex);
|
|
||||||
|
|
||||||
if (!allow_partial)
|
|
||||||
while (m_RecvQ->GetAvailableBytes() < count && !m_ErrorOccurred && !m_Eof)
|
|
||||||
m_CV.wait(lock);
|
|
||||||
|
|
||||||
HandleError();
|
|
||||||
|
|
||||||
return m_RecvQ->Peek(buffer, count, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t TlsStream::Read(void *buffer, size_t count, bool allow_partial)
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock lock(m_Mutex);
|
|
||||||
|
|
||||||
if (!allow_partial)
|
|
||||||
while (m_RecvQ->GetAvailableBytes() < count && !m_ErrorOccurred && !m_Eof)
|
|
||||||
m_CV.wait(lock);
|
|
||||||
|
|
||||||
HandleError();
|
|
||||||
|
|
||||||
return m_RecvQ->Read(buffer, count, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TlsStream::Write(const void *buffer, size_t count)
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock lock(m_Mutex);
|
|
||||||
|
|
||||||
m_SendQ->Write(buffer, count);
|
|
||||||
|
|
||||||
ChangeEvents(POLLIN|POLLOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TlsStream::Shutdown()
|
|
||||||
{
|
|
||||||
m_Shutdown = true;
|
|
||||||
ChangeEvents(POLLOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the stream.
|
|
||||||
*/
|
|
||||||
void TlsStream::Close()
|
|
||||||
{
|
|
||||||
CloseInternal(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TlsStream::CloseInternal(bool inDestructor)
|
|
||||||
{
|
|
||||||
if (m_Eof)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_Eof = true;
|
|
||||||
|
|
||||||
if (!inDestructor)
|
|
||||||
SignalDataAvailable();
|
|
||||||
|
|
||||||
SocketEvents::Unregister();
|
|
||||||
|
|
||||||
Stream::Close();
|
|
||||||
|
|
||||||
boost::mutex::scoped_lock lock(m_Mutex);
|
|
||||||
|
|
||||||
if (!m_SSL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* https://www.openssl.org/docs/manmaster/man3/SSL_shutdown.html
|
|
||||||
*
|
|
||||||
* It is recommended to do a bidirectional shutdown by checking
|
|
||||||
* the return value of SSL_shutdown() and call it again until
|
|
||||||
* it returns 1 or a fatal error. A maximum of 2x pending + 2x data
|
|
||||||
* is recommended.
|
|
||||||
*/
|
|
||||||
int rc = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
if ((rc = SSL_shutdown(m_SSL.get())))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_SSL.reset();
|
|
||||||
|
|
||||||
m_Socket->Close();
|
|
||||||
m_Socket.reset();
|
|
||||||
|
|
||||||
m_CV.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TlsStream::IsEof() const
|
|
||||||
{
|
|
||||||
return m_Eof && m_RecvQ->GetAvailableBytes() < 1u;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TlsStream::SupportsWaiting() const
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TlsStream::IsDataAvailable() const
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock lock(m_Mutex);
|
|
||||||
|
|
||||||
return m_RecvQ->GetAvailableBytes() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Socket::Ptr TlsStream::GetSocket() const
|
|
||||||
{
|
|
||||||
return m_Socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool UnbufferedAsioTlsStream::IsVerifyOK() const
|
bool UnbufferedAsioTlsStream::IsVerifyOK() const
|
||||||
{
|
{
|
||||||
return m_VerifyOK;
|
return m_VerifyOK;
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
#include "base/i2-base.hpp"
|
#include "base/i2-base.hpp"
|
||||||
#include "base/socket.hpp"
|
#include "base/socket.hpp"
|
||||||
#include "base/socketevents.hpp"
|
|
||||||
#include "base/stream.hpp"
|
#include "base/stream.hpp"
|
||||||
#include "base/tlsutility.hpp"
|
#include "base/tlsutility.hpp"
|
||||||
#include "base/fifo.hpp"
|
#include "base/fifo.hpp"
|
||||||
@ -20,86 +19,6 @@
|
|||||||
namespace icinga
|
namespace icinga
|
||||||
{
|
{
|
||||||
|
|
||||||
enum TlsAction
|
|
||||||
{
|
|
||||||
TlsActionNone,
|
|
||||||
TlsActionRead,
|
|
||||||
TlsActionWrite,
|
|
||||||
TlsActionHandshake
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A TLS stream.
|
|
||||||
*
|
|
||||||
* @ingroup base
|
|
||||||
*/
|
|
||||||
class TlsStream final : public SocketEvents
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DECLARE_PTR_TYPEDEFS(TlsStream);
|
|
||||||
|
|
||||||
TlsStream(const Socket::Ptr& socket, const String& hostname, ConnectionRole role, const std::shared_ptr<SSL_CTX>& sslContext = MakeSSLContext());
|
|
||||||
TlsStream(const Socket::Ptr& socket, const String& hostname, ConnectionRole role, const std::shared_ptr<boost::asio::ssl::context>& sslContext);
|
|
||||||
~TlsStream() override;
|
|
||||||
|
|
||||||
Socket::Ptr GetSocket() const;
|
|
||||||
|
|
||||||
std::shared_ptr<X509> GetClientCertificate() const;
|
|
||||||
std::shared_ptr<X509> GetPeerCertificate() const;
|
|
||||||
|
|
||||||
void Handshake();
|
|
||||||
|
|
||||||
void Close() override;
|
|
||||||
void Shutdown() override;
|
|
||||||
|
|
||||||
size_t Peek(void *buffer, size_t count, bool allow_partial = false) override;
|
|
||||||
size_t Read(void *buffer, size_t count, bool allow_partial = false) override;
|
|
||||||
void Write(const void *buffer, size_t count) override;
|
|
||||||
|
|
||||||
bool IsEof() const override;
|
|
||||||
|
|
||||||
bool SupportsWaiting() const override;
|
|
||||||
bool IsDataAvailable() const override;
|
|
||||||
|
|
||||||
bool IsVerifyOK() const;
|
|
||||||
String GetVerifyError() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<SSL> m_SSL;
|
|
||||||
bool m_Eof;
|
|
||||||
mutable boost::mutex m_Mutex;
|
|
||||||
mutable boost::condition_variable m_CV;
|
|
||||||
bool m_HandshakeOK;
|
|
||||||
bool m_VerifyOK;
|
|
||||||
String m_VerifyError;
|
|
||||||
int m_ErrorCode;
|
|
||||||
bool m_ErrorOccurred;
|
|
||||||
|
|
||||||
Socket::Ptr m_Socket;
|
|
||||||
ConnectionRole m_Role;
|
|
||||||
|
|
||||||
FIFO::Ptr m_SendQ;
|
|
||||||
FIFO::Ptr m_RecvQ;
|
|
||||||
|
|
||||||
TlsAction m_CurrentAction;
|
|
||||||
bool m_Retry;
|
|
||||||
bool m_Shutdown;
|
|
||||||
|
|
||||||
static int m_SSLIndex;
|
|
||||||
static bool m_SSLIndexInitialized;
|
|
||||||
|
|
||||||
TlsStream(const Socket::Ptr& socket, const String& hostname, ConnectionRole role, SSL_CTX* sslContext);
|
|
||||||
|
|
||||||
void OnEvent(int revents) override;
|
|
||||||
|
|
||||||
void HandleError() const;
|
|
||||||
|
|
||||||
static int ValidateCertificate(int preverify_ok, X509_STORE_CTX *ctx);
|
|
||||||
static void NullCertificateDeleter(X509 *certificate);
|
|
||||||
|
|
||||||
void CloseInternal(bool inDestructor);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct UnbufferedAsioTlsStreamParams
|
struct UnbufferedAsioTlsStreamParams
|
||||||
{
|
{
|
||||||
boost::asio::io_service& IoService;
|
boost::asio::io_service& IoService;
|
||||||
|
@ -129,25 +129,6 @@ static void SetupSslContext(SSL_CTX *sslContext, const String& pubkey, const Str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes an SSL context using the specified certificates.
|
|
||||||
*
|
|
||||||
* @param pubkey The public key.
|
|
||||||
* @param privkey The matching private key.
|
|
||||||
* @param cakey CA certificate chain file.
|
|
||||||
* @returns An SSL context.
|
|
||||||
*/
|
|
||||||
std::shared_ptr<SSL_CTX> MakeSSLContext(const String& pubkey, const String& privkey, const String& cakey)
|
|
||||||
{
|
|
||||||
InitializeOpenSSL();
|
|
||||||
|
|
||||||
std::shared_ptr<SSL_CTX> sslContext = std::shared_ptr<SSL_CTX>(SSL_CTX_new(SSLv23_method()), SSL_CTX_free);
|
|
||||||
|
|
||||||
SetupSslContext(sslContext.get(), pubkey, privkey, cakey);
|
|
||||||
|
|
||||||
return sslContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes an SSL context using the specified certificates.
|
* Initializes an SSL context using the specified certificates.
|
||||||
*
|
*
|
||||||
|
@ -21,25 +21,30 @@ namespace icinga
|
|||||||
{
|
{
|
||||||
|
|
||||||
void InitializeOpenSSL();
|
void InitializeOpenSSL();
|
||||||
std::shared_ptr<SSL_CTX> MakeSSLContext(const String& pubkey = String(), const String& privkey = String(), const String& cakey = String());
|
|
||||||
std::shared_ptr<boost::asio::ssl::context> MakeAsioSslContext(const String& pubkey = String(), const String& privkey = String(), const String& cakey = String());
|
std::shared_ptr<boost::asio::ssl::context> MakeAsioSslContext(const String& pubkey = String(), const String& privkey = String(), const String& cakey = String());
|
||||||
void AddCRLToSSLContext(const std::shared_ptr<boost::asio::ssl::context>& context, const String& crlPath);
|
void AddCRLToSSLContext(const std::shared_ptr<boost::asio::ssl::context>& context, const String& crlPath);
|
||||||
void SetCipherListToSSLContext(const std::shared_ptr<boost::asio::ssl::context>& context, const String& cipherList);
|
void SetCipherListToSSLContext(const std::shared_ptr<boost::asio::ssl::context>& context, const String& cipherList);
|
||||||
void SetTlsProtocolminToSSLContext(const std::shared_ptr<boost::asio::ssl::context>& context, const String& tlsProtocolmin);
|
void SetTlsProtocolminToSSLContext(const std::shared_ptr<boost::asio::ssl::context>& context, const String& tlsProtocolmin);
|
||||||
|
|
||||||
String GetCertificateCN(const std::shared_ptr<X509>& certificate);
|
String GetCertificateCN(const std::shared_ptr<X509>& certificate);
|
||||||
std::shared_ptr<X509> GetX509Certificate(const String& pemfile);
|
std::shared_ptr<X509> GetX509Certificate(const String& pemfile);
|
||||||
int MakeX509CSR(const String& cn, const String& keyfile, const String& csrfile = String(), const String& certfile = String(), bool ca = false);
|
int MakeX509CSR(const String& cn, const String& keyfile, const String& csrfile = String(), const String& certfile = String(), bool ca = false);
|
||||||
std::shared_ptr<X509> CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca);
|
std::shared_ptr<X509> CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca);
|
||||||
|
|
||||||
String GetIcingaCADir();
|
String GetIcingaCADir();
|
||||||
String CertificateToString(const std::shared_ptr<X509>& cert);
|
String CertificateToString(const std::shared_ptr<X509>& cert);
|
||||||
|
|
||||||
std::shared_ptr<X509> StringToCertificate(const String& cert);
|
std::shared_ptr<X509> StringToCertificate(const String& cert);
|
||||||
std::shared_ptr<X509> CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject);
|
std::shared_ptr<X509> CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject);
|
||||||
std::shared_ptr<X509> CreateCertIcingaCA(const std::shared_ptr<X509>& cert);
|
std::shared_ptr<X509> CreateCertIcingaCA(const std::shared_ptr<X509>& cert);
|
||||||
|
|
||||||
String PBKDF2_SHA1(const String& password, const String& salt, int iterations);
|
String PBKDF2_SHA1(const String& password, const String& salt, int iterations);
|
||||||
String PBKDF2_SHA256(const String& password, const String& salt, int iterations);
|
String PBKDF2_SHA256(const String& password, const String& salt, int iterations);
|
||||||
String SHA1(const String& s, bool binary = false);
|
String SHA1(const String& s, bool binary = false);
|
||||||
String SHA256(const String& s);
|
String SHA256(const String& s);
|
||||||
String RandomString(int length);
|
String RandomString(int length);
|
||||||
|
|
||||||
bool VerifyCertificate(const std::shared_ptr<X509>& caCertificate, const std::shared_ptr<X509>& certificate);
|
bool VerifyCertificate(const std::shared_ptr<X509>& caCertificate, const std::shared_ptr<X509>& certificate);
|
||||||
|
|
||||||
class openssl_error : virtual public std::exception, virtual public boost::exception { };
|
class openssl_error : virtual public std::exception, virtual public boost::exception { };
|
||||||
|
@ -9,6 +9,11 @@
|
|||||||
namespace icinga
|
namespace icinga
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A TCP socket. DEPRECATED - Use Boost ASIO instead.
|
||||||
|
*
|
||||||
|
* @ingroup base
|
||||||
|
*/
|
||||||
class UnixSocket final : public Socket
|
class UnixSocket final : public Socket
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "cli/consolecommand.hpp"
|
#include "cli/consolecommand.hpp"
|
||||||
#include "config/configcompiler.hpp"
|
#include "config/configcompiler.hpp"
|
||||||
#include "remote/apiclient.hpp"
|
|
||||||
#include "remote/consolehandler.hpp"
|
#include "remote/consolehandler.hpp"
|
||||||
#include "remote/url.hpp"
|
#include "remote/url.hpp"
|
||||||
#include "base/configwriter.hpp"
|
#include "base/configwriter.hpp"
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
#include "base/context.hpp"
|
#include "base/context.hpp"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include <boost/program_options.hpp>
|
#include <boost/program_options.hpp>
|
||||||
#include <boost/tuple/tuple.hpp>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@
|
|||||||
#include "base/application.hpp"
|
#include "base/application.hpp"
|
||||||
#include "base/context.hpp"
|
#include "base/context.hpp"
|
||||||
#include "base/statsfunction.hpp"
|
#include "base/statsfunction.hpp"
|
||||||
#include <boost/tuple/tuple.hpp>
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/algorithm/string/replace.hpp>
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
#include "base/configtype.hpp"
|
#include "base/configtype.hpp"
|
||||||
#include "base/exception.hpp"
|
#include "base/exception.hpp"
|
||||||
#include "base/statsfunction.hpp"
|
#include "base/statsfunction.hpp"
|
||||||
#include <boost/tuple/tuple.hpp>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
#include "base/exception.hpp"
|
#include "base/exception.hpp"
|
||||||
#include "base/context.hpp"
|
#include "base/context.hpp"
|
||||||
#include "base/statsfunction.hpp"
|
#include "base/statsfunction.hpp"
|
||||||
#include <boost/tuple/tuple.hpp>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
#include "icinga/service.hpp"
|
#include "icinga/service.hpp"
|
||||||
#include "base/configtype.hpp"
|
#include "base/configtype.hpp"
|
||||||
#include "base/objectlock.hpp"
|
#include "base/objectlock.hpp"
|
||||||
#include <boost/tuple/tuple.hpp>
|
|
||||||
|
|
||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
#include "base/objectlock.hpp"
|
#include "base/objectlock.hpp"
|
||||||
#include "base/json.hpp"
|
#include "base/json.hpp"
|
||||||
#include "base/utility.hpp"
|
#include "base/utility.hpp"
|
||||||
#include <boost/tuple/tuple.hpp>
|
|
||||||
|
|
||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
#include "icinga/service.hpp"
|
#include "icinga/service.hpp"
|
||||||
#include "base/configtype.hpp"
|
#include "base/configtype.hpp"
|
||||||
#include "base/objectlock.hpp"
|
#include "base/objectlock.hpp"
|
||||||
#include <boost/tuple/tuple.hpp>
|
|
||||||
|
|
||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
#include "base/objectlock.hpp"
|
#include "base/objectlock.hpp"
|
||||||
#include "base/convert.hpp"
|
#include "base/convert.hpp"
|
||||||
#include "base/utility.hpp"
|
#include "base/utility.hpp"
|
||||||
#include <boost/tuple/tuple.hpp>
|
|
||||||
#include <boost/algorithm/string/replace.hpp>
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
|
|
||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
#include "base/json.hpp"
|
#include "base/json.hpp"
|
||||||
#include "base/convert.hpp"
|
#include "base/convert.hpp"
|
||||||
#include "base/utility.hpp"
|
#include "base/utility.hpp"
|
||||||
#include <boost/tuple/tuple.hpp>
|
|
||||||
#include <boost/algorithm/string/replace.hpp>
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
|
|
||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
#include "base/utility.hpp"
|
#include "base/utility.hpp"
|
||||||
#include "base/convert.hpp"
|
#include "base/convert.hpp"
|
||||||
#include "base/logger.hpp"
|
#include "base/logger.hpp"
|
||||||
#include <boost/tuple/tuple.hpp>
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/algorithm/string/replace.hpp>
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
#include "base/logger.hpp"
|
#include "base/logger.hpp"
|
||||||
#include "base/application.hpp"
|
#include "base/application.hpp"
|
||||||
#include "base/objectlock.hpp"
|
#include "base/objectlock.hpp"
|
||||||
#include <boost/tuple/tuple.hpp>
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/algorithm/string/replace.hpp>
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
#include "base/json.hpp"
|
#include "base/json.hpp"
|
||||||
#include "base/convert.hpp"
|
#include "base/convert.hpp"
|
||||||
#include "base/utility.hpp"
|
#include "base/utility.hpp"
|
||||||
#include <boost/tuple/tuple.hpp>
|
|
||||||
#include <boost/algorithm/string/replace.hpp>
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
|
|
||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
#include "base/logger.hpp"
|
#include "base/logger.hpp"
|
||||||
#include "base/application.hpp"
|
#include "base/application.hpp"
|
||||||
#include "base/objectlock.hpp"
|
#include "base/objectlock.hpp"
|
||||||
#include <boost/tuple/tuple.hpp>
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/algorithm/string/replace.hpp>
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
#include "base/array.hpp"
|
#include "base/array.hpp"
|
||||||
#include "base/dictionary.hpp"
|
#include "base/dictionary.hpp"
|
||||||
#include <boost/algorithm/string/case_conv.hpp>
|
#include <boost/algorithm/string/case_conv.hpp>
|
||||||
#include <boost/tuple/tuple.hpp>
|
|
||||||
|
|
||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
|
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
#include "perfdata/elasticsearchwriter.hpp"
|
#include "perfdata/elasticsearchwriter.hpp"
|
||||||
#include "perfdata/elasticsearchwriter-ti.cpp"
|
#include "perfdata/elasticsearchwriter-ti.cpp"
|
||||||
#include "remote/url.hpp"
|
#include "remote/url.hpp"
|
||||||
#include "remote/httprequest.hpp"
|
|
||||||
#include "remote/httpresponse.hpp"
|
|
||||||
#include "icinga/compatutility.hpp"
|
#include "icinga/compatutility.hpp"
|
||||||
#include "icinga/service.hpp"
|
#include "icinga/service.hpp"
|
||||||
#include "icinga/checkcommand.hpp"
|
#include "icinga/checkcommand.hpp"
|
||||||
|
@ -28,6 +28,9 @@ REGISTER_TYPE(GraphiteWriter);
|
|||||||
|
|
||||||
REGISTER_STATSFUNCTION(GraphiteWriter, &GraphiteWriter::StatsFunc);
|
REGISTER_STATSFUNCTION(GraphiteWriter, &GraphiteWriter::StatsFunc);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Enable HA capabilities once the config object is loaded.
|
||||||
|
*/
|
||||||
void GraphiteWriter::OnConfigLoaded()
|
void GraphiteWriter::OnConfigLoaded()
|
||||||
{
|
{
|
||||||
ObjectImpl<GraphiteWriter>::OnConfigLoaded();
|
ObjectImpl<GraphiteWriter>::OnConfigLoaded();
|
||||||
@ -44,6 +47,12 @@ void GraphiteWriter::OnConfigLoaded()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature stats interface
|
||||||
|
*
|
||||||
|
* @param status Key value pairs for feature stats
|
||||||
|
* @param perfdata Array of PerfdataValue objects
|
||||||
|
*/
|
||||||
void GraphiteWriter::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata)
|
void GraphiteWriter::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata)
|
||||||
{
|
{
|
||||||
DictionaryData nodes;
|
DictionaryData nodes;
|
||||||
@ -65,6 +74,9 @@ void GraphiteWriter::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr&
|
|||||||
status->Set("graphitewriter", new Dictionary(std::move(nodes)));
|
status->Set("graphitewriter", new Dictionary(std::move(nodes)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume is equivalent to Start, but with HA capabilities to resume at runtime.
|
||||||
|
*/
|
||||||
void GraphiteWriter::Resume()
|
void GraphiteWriter::Resume()
|
||||||
{
|
{
|
||||||
ObjectImpl<GraphiteWriter>::Resume();
|
ObjectImpl<GraphiteWriter>::Resume();
|
||||||
@ -86,7 +98,9 @@ void GraphiteWriter::Resume()
|
|||||||
Checkable::OnNewCheckResult.connect(std::bind(&GraphiteWriter::CheckResultHandler, this, _1, _2));
|
Checkable::OnNewCheckResult.connect(std::bind(&GraphiteWriter::CheckResultHandler, this, _1, _2));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pause is equivalent to Stop, but with HA capabilities to resume at runtime. */
|
/**
|
||||||
|
* Pause is equivalent to Stop, but with HA capabilities to resume at runtime.
|
||||||
|
*/
|
||||||
void GraphiteWriter::Pause()
|
void GraphiteWriter::Pause()
|
||||||
{
|
{
|
||||||
m_ReconnectTimer.reset();
|
m_ReconnectTimer.reset();
|
||||||
@ -110,11 +124,21 @@ void GraphiteWriter::Pause()
|
|||||||
ObjectImpl<GraphiteWriter>::Pause();
|
ObjectImpl<GraphiteWriter>::Pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if method is called inside the WQ thread.
|
||||||
|
*/
|
||||||
void GraphiteWriter::AssertOnWorkQueue()
|
void GraphiteWriter::AssertOnWorkQueue()
|
||||||
{
|
{
|
||||||
ASSERT(m_WorkQueue.IsWorkerThread());
|
ASSERT(m_WorkQueue.IsWorkerThread());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception handler for the WQ.
|
||||||
|
*
|
||||||
|
* Closes the connection if connected.
|
||||||
|
*
|
||||||
|
* @param exp Exception pointer
|
||||||
|
*/
|
||||||
void GraphiteWriter::ExceptionHandler(boost::exception_ptr exp)
|
void GraphiteWriter::ExceptionHandler(boost::exception_ptr exp)
|
||||||
{
|
{
|
||||||
Log(LogCritical, "GraphiteWriter", "Exception during Graphite operation: Verify that your backend is operational!");
|
Log(LogCritical, "GraphiteWriter", "Exception during Graphite operation: Verify that your backend is operational!");
|
||||||
@ -123,12 +147,17 @@ void GraphiteWriter::ExceptionHandler(boost::exception_ptr exp)
|
|||||||
<< "Exception during Graphite operation: " << DiagnosticInformation(std::move(exp));
|
<< "Exception during Graphite operation: " << DiagnosticInformation(std::move(exp));
|
||||||
|
|
||||||
if (GetConnected()) {
|
if (GetConnected()) {
|
||||||
m_Stream->Close();
|
m_Stream->close();
|
||||||
|
|
||||||
SetConnected(false);
|
SetConnected(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconnect method, stops when the feature is paused in HA zones.
|
||||||
|
*
|
||||||
|
* Called inside the WQ.
|
||||||
|
*/
|
||||||
void GraphiteWriter::Reconnect()
|
void GraphiteWriter::Reconnect()
|
||||||
{
|
{
|
||||||
AssertOnWorkQueue();
|
AssertOnWorkQueue();
|
||||||
@ -141,6 +170,9 @@ void GraphiteWriter::Reconnect()
|
|||||||
ReconnectInternal();
|
ReconnectInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconnect method, connects to a TCP Stream
|
||||||
|
*/
|
||||||
void GraphiteWriter::ReconnectInternal()
|
void GraphiteWriter::ReconnectInternal()
|
||||||
{
|
{
|
||||||
double startTime = Utility::GetTime();
|
double startTime = Utility::GetTime();
|
||||||
@ -152,20 +184,17 @@ void GraphiteWriter::ReconnectInternal()
|
|||||||
if (GetConnected())
|
if (GetConnected())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
TcpSocket::Ptr socket = new TcpSocket();
|
|
||||||
|
|
||||||
Log(LogNotice, "GraphiteWriter")
|
Log(LogNotice, "GraphiteWriter")
|
||||||
<< "Reconnecting to Graphite on host '" << GetHost() << "' port '" << GetPort() << "'.";
|
<< "Reconnecting to Graphite on host '" << GetHost() << "' port '" << GetPort() << "'.";
|
||||||
|
|
||||||
try {
|
m_Stream = std::make_shared<AsioTcpStream>(IoEngine::Get().GetIoService());
|
||||||
socket->Connect(GetHost(), GetPort());
|
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
Log(LogCritical, "GraphiteWriter")
|
|
||||||
<< "Can't connect to Graphite on host '" << GetHost() << "' port '" << GetPort() << "'.";
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Stream = new NetworkStream(socket);
|
try {
|
||||||
|
icinga::Connect(m_Stream->lowest_layer(), GetHost(), GetPort());
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
Log(LogWarning, "GraphiteWriter")
|
||||||
|
<< "Can't connect to Graphite on host '" << GetHost() << "' port '" << GetPort() << ".'";
|
||||||
|
}
|
||||||
|
|
||||||
SetConnected(true);
|
SetConnected(true);
|
||||||
|
|
||||||
@ -173,14 +202,24 @@ void GraphiteWriter::ReconnectInternal()
|
|||||||
<< "Finished reconnecting to Graphite in " << std::setw(2) << Utility::GetTime() - startTime << " second(s).";
|
<< "Finished reconnecting to Graphite in " << std::setw(2) << Utility::GetTime() - startTime << " second(s).";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconnect handler called by the timer.
|
||||||
|
*
|
||||||
|
* Enqueues a reconnect task into the WQ.
|
||||||
|
*/
|
||||||
void GraphiteWriter::ReconnectTimerHandler()
|
void GraphiteWriter::ReconnectTimerHandler()
|
||||||
{
|
{
|
||||||
if (IsPaused())
|
if (IsPaused())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_WorkQueue.Enqueue(std::bind(&GraphiteWriter::Reconnect, this), PriorityNormal);
|
m_WorkQueue.Enqueue(std::bind(&GraphiteWriter::Reconnect, this), PriorityHigh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect the stream.
|
||||||
|
*
|
||||||
|
* Called inside the WQ.
|
||||||
|
*/
|
||||||
void GraphiteWriter::Disconnect()
|
void GraphiteWriter::Disconnect()
|
||||||
{
|
{
|
||||||
AssertOnWorkQueue();
|
AssertOnWorkQueue();
|
||||||
@ -188,16 +227,27 @@ void GraphiteWriter::Disconnect()
|
|||||||
DisconnectInternal();
|
DisconnectInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect the stream.
|
||||||
|
*
|
||||||
|
* Called outside the WQ.
|
||||||
|
*/
|
||||||
void GraphiteWriter::DisconnectInternal()
|
void GraphiteWriter::DisconnectInternal()
|
||||||
{
|
{
|
||||||
if (!GetConnected())
|
if (!GetConnected())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_Stream->Close();
|
m_Stream->close();
|
||||||
|
|
||||||
SetConnected(false);
|
SetConnected(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check result event handler, checks whether feature is not paused in HA setups.
|
||||||
|
*
|
||||||
|
* @param checkable Host/Service object
|
||||||
|
* @param cr Check result including performance data
|
||||||
|
*/
|
||||||
void GraphiteWriter::CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr)
|
void GraphiteWriter::CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr)
|
||||||
{
|
{
|
||||||
if (IsPaused())
|
if (IsPaused())
|
||||||
@ -206,6 +256,14 @@ void GraphiteWriter::CheckResultHandler(const Checkable::Ptr& checkable, const C
|
|||||||
m_WorkQueue.Enqueue(std::bind(&GraphiteWriter::CheckResultHandlerInternal, this, checkable, cr));
|
m_WorkQueue.Enqueue(std::bind(&GraphiteWriter::CheckResultHandlerInternal, this, checkable, cr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check result event handler, prepares metadata and perfdata values and calls Send*()
|
||||||
|
*
|
||||||
|
* Called inside the WQ.
|
||||||
|
*
|
||||||
|
* @param checkable Host/Service object
|
||||||
|
* @param cr Check result including performance data
|
||||||
|
*/
|
||||||
void GraphiteWriter::CheckResultHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr)
|
void GraphiteWriter::CheckResultHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr)
|
||||||
{
|
{
|
||||||
AssertOnWorkQueue();
|
AssertOnWorkQueue();
|
||||||
@ -262,6 +320,14 @@ void GraphiteWriter::CheckResultHandlerInternal(const Checkable::Ptr& checkable,
|
|||||||
SendPerfdata(checkable, prefixPerfdata, cr, ts);
|
SendPerfdata(checkable, prefixPerfdata, cr, ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse performance data from check result and call SendMetric()
|
||||||
|
*
|
||||||
|
* @param checkable Host/service object
|
||||||
|
* @param prefix Metric prefix string
|
||||||
|
* @param cr Check result including performance data
|
||||||
|
* @param ts Timestamp when the check result was created
|
||||||
|
*/
|
||||||
void GraphiteWriter::SendPerfdata(const Checkable::Ptr& checkable, const String& prefix, const CheckResult::Ptr& cr, double ts)
|
void GraphiteWriter::SendPerfdata(const Checkable::Ptr& checkable, const String& prefix, const CheckResult::Ptr& cr, double ts)
|
||||||
{
|
{
|
||||||
Array::Ptr perfdata = cr->GetPerformanceData();
|
Array::Ptr perfdata = cr->GetPerformanceData();
|
||||||
@ -306,8 +372,19 @@ void GraphiteWriter::SendPerfdata(const Checkable::Ptr& checkable, const String&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes metric data and sends to Graphite
|
||||||
|
*
|
||||||
|
* @param checkable Host/service object
|
||||||
|
* @param prefix Computed metric prefix string
|
||||||
|
* @param name Metric name
|
||||||
|
* @param value Metric value
|
||||||
|
* @param ts Timestamp when the check result was created
|
||||||
|
*/
|
||||||
void GraphiteWriter::SendMetric(const Checkable::Ptr& checkable, const String& prefix, const String& name, double value, double ts)
|
void GraphiteWriter::SendMetric(const Checkable::Ptr& checkable, const String& prefix, const String& name, double value, double ts)
|
||||||
{
|
{
|
||||||
|
namespace asio = boost::asio;
|
||||||
|
|
||||||
std::ostringstream msgbuf;
|
std::ostringstream msgbuf;
|
||||||
msgbuf << prefix << "." << name << " " << Convert::ToString(value) << " " << static_cast<long>(ts);
|
msgbuf << prefix << "." << name << " " << Convert::ToString(value) << " " << static_cast<long>(ts);
|
||||||
|
|
||||||
@ -316,7 +393,6 @@ void GraphiteWriter::SendMetric(const Checkable::Ptr& checkable, const String& p
|
|||||||
|
|
||||||
// do not send \n to debug log
|
// do not send \n to debug log
|
||||||
msgbuf << "\n";
|
msgbuf << "\n";
|
||||||
String metric = msgbuf.str();
|
|
||||||
|
|
||||||
boost::mutex::scoped_lock lock(m_StreamMutex);
|
boost::mutex::scoped_lock lock(m_StreamMutex);
|
||||||
|
|
||||||
@ -324,7 +400,8 @@ void GraphiteWriter::SendMetric(const Checkable::Ptr& checkable, const String& p
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
m_Stream->Write(metric.CStr(), metric.GetLength());
|
asio::write(*m_Stream, asio::buffer(msgbuf.str()));
|
||||||
|
m_Stream->flush();
|
||||||
} catch (const std::exception& ex) {
|
} catch (const std::exception& ex) {
|
||||||
Log(LogCritical, "GraphiteWriter")
|
Log(LogCritical, "GraphiteWriter")
|
||||||
<< "Cannot write to TCP socket on host '" << GetHost() << "' port '" << GetPort() << "'.";
|
<< "Cannot write to TCP socket on host '" << GetHost() << "' port '" << GetPort() << "'.";
|
||||||
@ -333,6 +410,14 @@ void GraphiteWriter::SendMetric(const Checkable::Ptr& checkable, const String& p
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape metric tree elements
|
||||||
|
*
|
||||||
|
* Dots are not allowed, e.g. in host names
|
||||||
|
*
|
||||||
|
* @param str Metric part name
|
||||||
|
* @return Escape string
|
||||||
|
*/
|
||||||
String GraphiteWriter::EscapeMetric(const String& str)
|
String GraphiteWriter::EscapeMetric(const String& str)
|
||||||
{
|
{
|
||||||
String result = str;
|
String result = str;
|
||||||
@ -346,6 +431,14 @@ String GraphiteWriter::EscapeMetric(const String& str)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape metric label
|
||||||
|
*
|
||||||
|
* Dots are allowed - users can create trees from perfdata labels
|
||||||
|
*
|
||||||
|
* @param str Metric label name
|
||||||
|
* @return Escaped string
|
||||||
|
*/
|
||||||
String GraphiteWriter::EscapeMetricLabel(const String& str)
|
String GraphiteWriter::EscapeMetricLabel(const String& str)
|
||||||
{
|
{
|
||||||
String result = str;
|
String result = str;
|
||||||
@ -359,6 +452,12 @@ String GraphiteWriter::EscapeMetricLabel(const String& str)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape macro metrics found via host/service name templates
|
||||||
|
*
|
||||||
|
* @param value Array or string with macro metric names
|
||||||
|
* @return Escaped string. Arrays are joined with dots.
|
||||||
|
*/
|
||||||
Value GraphiteWriter::EscapeMacroMetric(const Value& value)
|
Value GraphiteWriter::EscapeMacroMetric(const Value& value)
|
||||||
{
|
{
|
||||||
if (value.IsObjectType<Array>()) {
|
if (value.IsObjectType<Array>()) {
|
||||||
@ -375,6 +474,12 @@ Value GraphiteWriter::EscapeMacroMetric(const Value& value)
|
|||||||
return EscapeMetric(value);
|
return EscapeMetric(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the configuration setting 'host_name_template'
|
||||||
|
*
|
||||||
|
* @param lvalue String containing runtime macros.
|
||||||
|
* @param utils Helper, unused
|
||||||
|
*/
|
||||||
void GraphiteWriter::ValidateHostNameTemplate(const Lazy<String>& lvalue, const ValidationUtils& utils)
|
void GraphiteWriter::ValidateHostNameTemplate(const Lazy<String>& lvalue, const ValidationUtils& utils)
|
||||||
{
|
{
|
||||||
ObjectImpl<GraphiteWriter>::ValidateHostNameTemplate(lvalue, utils);
|
ObjectImpl<GraphiteWriter>::ValidateHostNameTemplate(lvalue, utils);
|
||||||
@ -383,6 +488,12 @@ void GraphiteWriter::ValidateHostNameTemplate(const Lazy<String>& lvalue, const
|
|||||||
BOOST_THROW_EXCEPTION(ValidationError(this, { "host_name_template" }, "Closing $ not found in macro format string '" + lvalue() + "'."));
|
BOOST_THROW_EXCEPTION(ValidationError(this, { "host_name_template" }, "Closing $ not found in macro format string '" + lvalue() + "'."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the configuration setting 'service_name_template'
|
||||||
|
*
|
||||||
|
* @param lvalue String containing runtime macros.
|
||||||
|
* @param utils Helper, unused
|
||||||
|
*/
|
||||||
void GraphiteWriter::ValidateServiceNameTemplate(const Lazy<String>& lvalue, const ValidationUtils& utils)
|
void GraphiteWriter::ValidateServiceNameTemplate(const Lazy<String>& lvalue, const ValidationUtils& utils)
|
||||||
{
|
{
|
||||||
ObjectImpl<GraphiteWriter>::ValidateServiceNameTemplate(lvalue, utils);
|
ObjectImpl<GraphiteWriter>::ValidateServiceNameTemplate(lvalue, utils);
|
||||||
|
@ -37,7 +37,7 @@ protected:
|
|||||||
void Pause() override;
|
void Pause() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Stream::Ptr m_Stream;
|
std::shared_ptr<AsioTcpStream> m_Stream;
|
||||||
boost::mutex m_StreamMutex;
|
boost::mutex m_StreamMutex;
|
||||||
WorkQueue m_WorkQueue{10000000, 1};
|
WorkQueue m_WorkQueue{10000000, 1};
|
||||||
|
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
#include "perfdata/influxdbwriter.hpp"
|
#include "perfdata/influxdbwriter.hpp"
|
||||||
#include "perfdata/influxdbwriter-ti.cpp"
|
#include "perfdata/influxdbwriter-ti.cpp"
|
||||||
#include "remote/url.hpp"
|
#include "remote/url.hpp"
|
||||||
#include "remote/httprequest.hpp"
|
|
||||||
#include "remote/httpresponse.hpp"
|
|
||||||
#include "icinga/service.hpp"
|
#include "icinga/service.hpp"
|
||||||
#include "icinga/macroprocessor.hpp"
|
#include "icinga/macroprocessor.hpp"
|
||||||
#include "icinga/icingaapplication.hpp"
|
#include "icinga/icingaapplication.hpp"
|
||||||
|
@ -28,6 +28,9 @@ REGISTER_TYPE(OpenTsdbWriter);
|
|||||||
|
|
||||||
REGISTER_STATSFUNCTION(OpenTsdbWriter, &OpenTsdbWriter::StatsFunc);
|
REGISTER_STATSFUNCTION(OpenTsdbWriter, &OpenTsdbWriter::StatsFunc);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Enable HA capabilities once the config object is loaded.
|
||||||
|
*/
|
||||||
void OpenTsdbWriter::OnConfigLoaded()
|
void OpenTsdbWriter::OnConfigLoaded()
|
||||||
{
|
{
|
||||||
ObjectImpl<OpenTsdbWriter>::OnConfigLoaded();
|
ObjectImpl<OpenTsdbWriter>::OnConfigLoaded();
|
||||||
@ -42,17 +45,27 @@ void OpenTsdbWriter::OnConfigLoaded()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature stats interface
|
||||||
|
*
|
||||||
|
* @param status Key value pairs for feature stats
|
||||||
|
*/
|
||||||
void OpenTsdbWriter::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr&)
|
void OpenTsdbWriter::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr&)
|
||||||
{
|
{
|
||||||
DictionaryData nodes;
|
DictionaryData nodes;
|
||||||
|
|
||||||
for (const OpenTsdbWriter::Ptr& opentsdbwriter : ConfigType::GetObjectsByType<OpenTsdbWriter>()) {
|
for (const OpenTsdbWriter::Ptr& opentsdbwriter : ConfigType::GetObjectsByType<OpenTsdbWriter>()) {
|
||||||
nodes.emplace_back(opentsdbwriter->GetName(), 1); //add more stats
|
nodes.emplace_back(opentsdbwriter->GetName(), new Dictionary({
|
||||||
|
{ "connected", opentsdbwriter->GetConnected() }
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
status->Set("opentsdbwriter", new Dictionary(std::move(nodes)));
|
status->Set("opentsdbwriter", new Dictionary(std::move(nodes)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume is equivalent to Start, but with HA capabilities to resume at runtime.
|
||||||
|
*/
|
||||||
void OpenTsdbWriter::Resume()
|
void OpenTsdbWriter::Resume()
|
||||||
{
|
{
|
||||||
ObjectImpl<OpenTsdbWriter>::Resume();
|
ObjectImpl<OpenTsdbWriter>::Resume();
|
||||||
@ -69,7 +82,9 @@ void OpenTsdbWriter::Resume()
|
|||||||
Service::OnNewCheckResult.connect(std::bind(&OpenTsdbWriter::CheckResultHandler, this, _1, _2));
|
Service::OnNewCheckResult.connect(std::bind(&OpenTsdbWriter::CheckResultHandler, this, _1, _2));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pause is equivalent to Stop, but with HA capabilities to resume at runtime. */
|
/**
|
||||||
|
* Pause is equivalent to Stop, but with HA capabilities to resume at runtime.
|
||||||
|
*/
|
||||||
void OpenTsdbWriter::Pause()
|
void OpenTsdbWriter::Pause()
|
||||||
{
|
{
|
||||||
m_ReconnectTimer.reset();
|
m_ReconnectTimer.reset();
|
||||||
@ -77,33 +92,54 @@ void OpenTsdbWriter::Pause()
|
|||||||
Log(LogInformation, "OpentsdbWriter")
|
Log(LogInformation, "OpentsdbWriter")
|
||||||
<< "'" << GetName() << "' paused.";
|
<< "'" << GetName() << "' paused.";
|
||||||
|
|
||||||
|
m_Stream->close();
|
||||||
|
|
||||||
|
SetConnected(false);
|
||||||
|
|
||||||
ObjectImpl<OpenTsdbWriter>::Pause();
|
ObjectImpl<OpenTsdbWriter>::Pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconnect handler called by the timer.
|
||||||
|
* Handles TLS
|
||||||
|
*/
|
||||||
void OpenTsdbWriter::ReconnectTimerHandler()
|
void OpenTsdbWriter::ReconnectTimerHandler()
|
||||||
{
|
{
|
||||||
if (IsPaused())
|
if (IsPaused())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (m_Stream)
|
SetShouldConnect(true);
|
||||||
return;
|
|
||||||
|
|
||||||
TcpSocket::Ptr socket = new TcpSocket();
|
if (GetConnected())
|
||||||
|
return;
|
||||||
|
|
||||||
Log(LogNotice, "OpenTsdbWriter")
|
Log(LogNotice, "OpenTsdbWriter")
|
||||||
<< "Reconnect to OpenTSDB TSD on host '" << GetHost() << "' port '" << GetPort() << "'.";
|
<< "Reconnect to OpenTSDB TSD on host '" << GetHost() << "' port '" << GetPort() << "'.";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We're using telnet as input method. Future PRs may change this into using the HTTP API.
|
||||||
|
* http://opentsdb.net/docs/build/html/user_guide/writing/index.html#telnet
|
||||||
|
*/
|
||||||
|
|
||||||
|
m_Stream = std::make_shared<AsioTcpStream>(IoEngine::Get().GetIoService());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
socket->Connect(GetHost(), GetPort());
|
icinga::Connect(m_Stream->lowest_layer(), GetHost(), GetPort());
|
||||||
} catch (std::exception&) {
|
} catch (const std::exception& ex) {
|
||||||
Log(LogCritical, "OpenTsdbWriter")
|
Log(LogWarning, "OpenTsdbWriter")
|
||||||
<< "Can't connect to OpenTSDB TSD on host '" << GetHost() << "' port '" << GetPort() << "'.";
|
<< "Can't connect to OpenTSDB on host '" << GetHost() << "' port '" << GetPort() << ".'";
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_Stream = new NetworkStream(socket);
|
SetConnected(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registered check result handler processing data.
|
||||||
|
* Calculates tags from the config.
|
||||||
|
*
|
||||||
|
* @param checkable Host/service object
|
||||||
|
* @param cr Check result
|
||||||
|
*/
|
||||||
void OpenTsdbWriter::CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr)
|
void OpenTsdbWriter::CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr)
|
||||||
{
|
{
|
||||||
if (IsPaused())
|
if (IsPaused())
|
||||||
@ -165,6 +201,15 @@ void OpenTsdbWriter::CheckResultHandler(const Checkable::Ptr& checkable, const C
|
|||||||
SendMetric(checkable, metric + ".execution_time", tags, cr->CalculateExecutionTime(), ts);
|
SendMetric(checkable, metric + ".execution_time", tags, cr->CalculateExecutionTime(), ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse and send performance data metrics to OpenTSDB
|
||||||
|
*
|
||||||
|
* @param checkable Host/service object
|
||||||
|
* @param metric Full metric name
|
||||||
|
* @param tags Tag key pairs
|
||||||
|
* @param cr Check result containing performance data
|
||||||
|
* @param ts Timestamp when the check result was received
|
||||||
|
*/
|
||||||
void OpenTsdbWriter::SendPerfdata(const Checkable::Ptr& checkable, const String& metric,
|
void OpenTsdbWriter::SendPerfdata(const Checkable::Ptr& checkable, const String& metric,
|
||||||
const std::map<String, String>& tags, const CheckResult::Ptr& cr, double ts)
|
const std::map<String, String>& tags, const CheckResult::Ptr& cr, double ts)
|
||||||
{
|
{
|
||||||
@ -209,6 +254,15 @@ void OpenTsdbWriter::SendPerfdata(const Checkable::Ptr& checkable, const String&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send given metric to OpenTSDB
|
||||||
|
*
|
||||||
|
* @param checkable Host/service object
|
||||||
|
* @param metric Full metric name
|
||||||
|
* @param tags Tag key pairs
|
||||||
|
* @param value Floating point metric value
|
||||||
|
* @param ts Timestamp where the metric was received from the check result
|
||||||
|
*/
|
||||||
void OpenTsdbWriter::SendMetric(const Checkable::Ptr& checkable, const String& metric,
|
void OpenTsdbWriter::SendMetric(const Checkable::Ptr& checkable, const String& metric,
|
||||||
const std::map<String, String>& tags, double value, double ts)
|
const std::map<String, String>& tags, double value, double ts)
|
||||||
{
|
{
|
||||||
@ -220,7 +274,7 @@ void OpenTsdbWriter::SendMetric(const Checkable::Ptr& checkable, const String& m
|
|||||||
|
|
||||||
std::ostringstream msgbuf;
|
std::ostringstream msgbuf;
|
||||||
/*
|
/*
|
||||||
* must be (http://opentsdb.net/docs/build/html/user_guide/writing.html)
|
* must be (http://opentsdb.net/docs/build/html/user_guide/query/timeseries.html)
|
||||||
* put <metric> <timestamp> <value> <tagk1=tagv1[ tagk2=tagv2 ...tagkN=tagvN]>
|
* put <metric> <timestamp> <value> <tagk1=tagv1[ tagk2=tagv2 ...tagkN=tagvN]>
|
||||||
* "tags" must include at least one tag, we use "host=HOSTNAME"
|
* "tags" must include at least one tag, we use "host=HOSTNAME"
|
||||||
*/
|
*/
|
||||||
@ -235,21 +289,27 @@ void OpenTsdbWriter::SendMetric(const Checkable::Ptr& checkable, const String& m
|
|||||||
|
|
||||||
ObjectLock olock(this);
|
ObjectLock olock(this);
|
||||||
|
|
||||||
if (!m_Stream)
|
if (!GetConnected())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
m_Stream->Write(put.CStr(), put.GetLength());
|
Log(LogDebug, "OpenTsdbWriter")
|
||||||
|
<< "Checkable '" << checkable->GetName() << "' sending message '" << put << "'.";
|
||||||
|
|
||||||
|
boost::asio::write(*m_Stream, boost::asio::buffer(msgbuf.str()));
|
||||||
|
m_Stream->flush();
|
||||||
} catch (const std::exception& ex) {
|
} catch (const std::exception& ex) {
|
||||||
Log(LogCritical, "OpenTsdbWriter")
|
Log(LogCritical, "OpenTsdbWriter")
|
||||||
<< "Cannot write to OpenTSDB TSD on host '" << GetHost() << "' port '" << GetPort() + "'.";
|
<< "Cannot write to TCP socket on host '" << GetHost() << "' port '" << GetPort() << "'.";
|
||||||
|
|
||||||
m_Stream.reset();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* for metric and tag name rules, see
|
/**
|
||||||
* http://opentsdb.net/docs/build/html/user_guide/writing.html#metrics-and-tags
|
* Escape tags for OpenTSDB
|
||||||
|
* http://opentsdb.net/docs/build/html/user_guide/query/timeseries.html#precisions-on-metrics-and-tags
|
||||||
|
*
|
||||||
|
* @param str Tag name
|
||||||
|
* @return Escaped tag
|
||||||
*/
|
*/
|
||||||
String OpenTsdbWriter::EscapeTag(const String& str)
|
String OpenTsdbWriter::EscapeTag(const String& str)
|
||||||
{
|
{
|
||||||
@ -261,6 +321,13 @@ String OpenTsdbWriter::EscapeTag(const String& str)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape metric name for OpenTSDB
|
||||||
|
* http://opentsdb.net/docs/build/html/user_guide/query/timeseries.html#precisions-on-metrics-and-tags
|
||||||
|
*
|
||||||
|
* @param str Metric name
|
||||||
|
* @return Escaped metric
|
||||||
|
*/
|
||||||
String OpenTsdbWriter::EscapeMetric(const String& str)
|
String OpenTsdbWriter::EscapeMetric(const String& str)
|
||||||
{
|
{
|
||||||
String result = str;
|
String result = str;
|
||||||
|
@ -32,7 +32,7 @@ protected:
|
|||||||
void Pause() override;
|
void Pause() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Stream::Ptr m_Stream;
|
std::shared_ptr<AsioTcpStream> m_Stream;
|
||||||
|
|
||||||
Timer::Ptr m_ReconnectTimer;
|
Timer::Ptr m_ReconnectTimer;
|
||||||
|
|
||||||
|
@ -20,6 +20,11 @@ class OpenTsdbWriter : ConfigObject
|
|||||||
[config] bool enable_ha {
|
[config] bool enable_ha {
|
||||||
default {{{ return false; }}}
|
default {{{ return false; }}}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[no_user_modify] bool connected;
|
||||||
|
[no_user_modify] bool should_connect {
|
||||||
|
default {{{ return true; }}}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ set(remote_SOURCES
|
|||||||
i2-remote.hpp
|
i2-remote.hpp
|
||||||
actionshandler.cpp actionshandler.hpp
|
actionshandler.cpp actionshandler.hpp
|
||||||
apiaction.cpp apiaction.hpp
|
apiaction.cpp apiaction.hpp
|
||||||
apiclient.cpp apiclient.hpp
|
|
||||||
apifunction.cpp apifunction.hpp
|
apifunction.cpp apifunction.hpp
|
||||||
apilistener.cpp apilistener.hpp apilistener-ti.hpp apilistener-configsync.cpp apilistener-filesync.cpp
|
apilistener.cpp apilistener.hpp apilistener-ti.hpp apilistener-configsync.cpp apilistener-filesync.cpp
|
||||||
apilistener-authority.cpp
|
apilistener-authority.cpp
|
||||||
@ -26,11 +25,7 @@ set(remote_SOURCES
|
|||||||
eventqueue.cpp eventqueue.hpp
|
eventqueue.cpp eventqueue.hpp
|
||||||
eventshandler.cpp eventshandler.hpp
|
eventshandler.cpp eventshandler.hpp
|
||||||
filterutility.cpp filterutility.hpp
|
filterutility.cpp filterutility.hpp
|
||||||
httpchunkedencoding.cpp httpchunkedencoding.hpp
|
|
||||||
httpclientconnection.cpp httpclientconnection.hpp
|
|
||||||
httphandler.cpp httphandler.hpp
|
httphandler.cpp httphandler.hpp
|
||||||
httprequest.cpp httprequest.hpp
|
|
||||||
httpresponse.cpp httpresponse.hpp
|
|
||||||
httpserverconnection.cpp httpserverconnection.hpp
|
httpserverconnection.cpp httpserverconnection.hpp
|
||||||
httputility.cpp httputility.hpp
|
httputility.cpp httputility.hpp
|
||||||
infohandler.cpp infohandler.hpp
|
infohandler.cpp infohandler.hpp
|
||||||
|
@ -1,164 +0,0 @@
|
|||||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
|
||||||
|
|
||||||
#include "remote/apiclient.hpp"
|
|
||||||
#include "base/base64.hpp"
|
|
||||||
#include "base/json.hpp"
|
|
||||||
#include "base/logger.hpp"
|
|
||||||
#include "base/exception.hpp"
|
|
||||||
#include "base/convert.hpp"
|
|
||||||
|
|
||||||
using namespace icinga;
|
|
||||||
|
|
||||||
ApiClient::ApiClient(const String& host, const String& port,
|
|
||||||
String user, String password)
|
|
||||||
: m_Connection(new HttpClientConnection(host, port, true)), m_User(std::move(user)), m_Password(std::move(password))
|
|
||||||
{
|
|
||||||
m_Connection->Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiClient::ExecuteScript(const String& session, const String& command, bool sandboxed,
|
|
||||||
const ExecuteScriptCompletionCallback& callback) const
|
|
||||||
{
|
|
||||||
Url::Ptr url = new Url();
|
|
||||||
url->SetScheme("https");
|
|
||||||
url->SetHost(m_Connection->GetHost());
|
|
||||||
url->SetPort(m_Connection->GetPort());
|
|
||||||
url->SetPath({ "v1", "console", "execute-script" });
|
|
||||||
|
|
||||||
url->SetQuery({
|
|
||||||
{"session", session},
|
|
||||||
{"command", command},
|
|
||||||
{"sandboxed", sandboxed ? "1" : "0"}
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
std::shared_ptr<HttpRequest> req = m_Connection->NewRequest();
|
|
||||||
req->RequestMethod = "POST";
|
|
||||||
req->RequestUrl = url;
|
|
||||||
req->AddHeader("Authorization", "Basic " + Base64::Encode(m_User + ":" + m_Password));
|
|
||||||
req->AddHeader("Accept", "application/json");
|
|
||||||
m_Connection->SubmitRequest(req, std::bind(ExecuteScriptHttpCompletionCallback, _1, _2, callback));
|
|
||||||
} catch (const std::exception&) {
|
|
||||||
callback(boost::current_exception(), Empty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiClient::ExecuteScriptHttpCompletionCallback(HttpRequest& request,
|
|
||||||
HttpResponse& response, const ExecuteScriptCompletionCallback& callback)
|
|
||||||
{
|
|
||||||
Dictionary::Ptr result;
|
|
||||||
|
|
||||||
String body;
|
|
||||||
char buffer[1024];
|
|
||||||
size_t count;
|
|
||||||
|
|
||||||
while ((count = response.ReadBody(buffer, sizeof(buffer))) > 0)
|
|
||||||
body += String(buffer, buffer + count);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (response.StatusCode < 200 || response.StatusCode > 299) {
|
|
||||||
std::string message = "HTTP request failed; Code: " + Convert::ToString(response.StatusCode) + "; Body: " + body;
|
|
||||||
|
|
||||||
BOOST_THROW_EXCEPTION(ScriptError(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
result = JsonDecode(body);
|
|
||||||
|
|
||||||
Array::Ptr results = result->Get("results");
|
|
||||||
Value result;
|
|
||||||
String errorMessage = "Unexpected result from API.";
|
|
||||||
|
|
||||||
if (results && results->GetLength() > 0) {
|
|
||||||
Dictionary::Ptr resultInfo = results->Get(0);
|
|
||||||
errorMessage = resultInfo->Get("status");
|
|
||||||
|
|
||||||
if (resultInfo->Get("code") >= 200 && resultInfo->Get("code") <= 299) {
|
|
||||||
result = resultInfo->Get("result");
|
|
||||||
} else {
|
|
||||||
DebugInfo di;
|
|
||||||
Dictionary::Ptr debugInfo = resultInfo->Get("debug_info");
|
|
||||||
if (debugInfo) {
|
|
||||||
di.Path = debugInfo->Get("path");
|
|
||||||
di.FirstLine = debugInfo->Get("first_line");
|
|
||||||
di.FirstColumn = debugInfo->Get("first_column");
|
|
||||||
di.LastLine = debugInfo->Get("last_line");
|
|
||||||
di.LastColumn = debugInfo->Get("last_column");
|
|
||||||
}
|
|
||||||
bool incompleteExpression = resultInfo->Get("incomplete_expression");
|
|
||||||
BOOST_THROW_EXCEPTION(ScriptError(errorMessage, di, incompleteExpression));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(boost::exception_ptr(), result);
|
|
||||||
} catch (const std::exception&) {
|
|
||||||
callback(boost::current_exception(), Empty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiClient::AutocompleteScript(const String& session, const String& command, bool sandboxed,
|
|
||||||
const AutocompleteScriptCompletionCallback& callback) const
|
|
||||||
{
|
|
||||||
Url::Ptr url = new Url();
|
|
||||||
url->SetScheme("https");
|
|
||||||
url->SetHost(m_Connection->GetHost());
|
|
||||||
url->SetPort(m_Connection->GetPort());
|
|
||||||
url->SetPath({ "v1", "console", "auto-complete-script" });
|
|
||||||
|
|
||||||
url->SetQuery({
|
|
||||||
{"session", session},
|
|
||||||
{"command", command},
|
|
||||||
{"sandboxed", sandboxed ? "1" : "0"}
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
std::shared_ptr<HttpRequest> req = m_Connection->NewRequest();
|
|
||||||
req->RequestMethod = "POST";
|
|
||||||
req->RequestUrl = url;
|
|
||||||
req->AddHeader("Authorization", "Basic " + Base64::Encode(m_User + ":" + m_Password));
|
|
||||||
req->AddHeader("Accept", "application/json");
|
|
||||||
m_Connection->SubmitRequest(req, std::bind(AutocompleteScriptHttpCompletionCallback, _1, _2, callback));
|
|
||||||
} catch (const std::exception&) {
|
|
||||||
callback(boost::current_exception(), nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiClient::AutocompleteScriptHttpCompletionCallback(HttpRequest& request,
|
|
||||||
HttpResponse& response, const AutocompleteScriptCompletionCallback& callback)
|
|
||||||
{
|
|
||||||
Dictionary::Ptr result;
|
|
||||||
|
|
||||||
String body;
|
|
||||||
char buffer[1024];
|
|
||||||
size_t count;
|
|
||||||
|
|
||||||
while ((count = response.ReadBody(buffer, sizeof(buffer))) > 0)
|
|
||||||
body += String(buffer, buffer + count);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (response.StatusCode < 200 || response.StatusCode > 299) {
|
|
||||||
std::string message = "HTTP request failed; Code: " + Convert::ToString(response.StatusCode) + "; Body: " + body;
|
|
||||||
|
|
||||||
BOOST_THROW_EXCEPTION(ScriptError(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
result = JsonDecode(body);
|
|
||||||
|
|
||||||
Array::Ptr results = result->Get("results");
|
|
||||||
Array::Ptr suggestions;
|
|
||||||
String errorMessage = "Unexpected result from API.";
|
|
||||||
|
|
||||||
if (results && results->GetLength() > 0) {
|
|
||||||
Dictionary::Ptr resultInfo = results->Get(0);
|
|
||||||
errorMessage = resultInfo->Get("status");
|
|
||||||
|
|
||||||
if (resultInfo->Get("code") >= 200 && resultInfo->Get("code") <= 299)
|
|
||||||
suggestions = resultInfo->Get("suggestions");
|
|
||||||
else
|
|
||||||
BOOST_THROW_EXCEPTION(ScriptError(errorMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(boost::exception_ptr(), suggestions);
|
|
||||||
} catch (const std::exception&) {
|
|
||||||
callback(boost::current_exception(), nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
|
||||||
|
|
||||||
#ifndef APICLIENT_H
|
|
||||||
#define APICLIENT_H
|
|
||||||
|
|
||||||
#include "remote/httpclientconnection.hpp"
|
|
||||||
#include "base/value.hpp"
|
|
||||||
#include "base/exception.hpp"
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace icinga
|
|
||||||
{
|
|
||||||
|
|
||||||
class ApiClient : public Object
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DECLARE_PTR_TYPEDEFS(ApiClient);
|
|
||||||
|
|
||||||
ApiClient(const String& host, const String& port,
|
|
||||||
String user, String password);
|
|
||||||
|
|
||||||
typedef std::function<void(boost::exception_ptr, const Value&)> ExecuteScriptCompletionCallback;
|
|
||||||
void ExecuteScript(const String& session, const String& command, bool sandboxed,
|
|
||||||
const ExecuteScriptCompletionCallback& callback) const;
|
|
||||||
|
|
||||||
typedef std::function<void(boost::exception_ptr, const Array::Ptr&)> AutocompleteScriptCompletionCallback;
|
|
||||||
void AutocompleteScript(const String& session, const String& command, bool sandboxed,
|
|
||||||
const AutocompleteScriptCompletionCallback& callback) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
HttpClientConnection::Ptr m_Connection;
|
|
||||||
String m_User;
|
|
||||||
String m_Password;
|
|
||||||
|
|
||||||
static void ExecuteScriptHttpCompletionCallback(HttpRequest& request,
|
|
||||||
HttpResponse& response, const ExecuteScriptCompletionCallback& callback);
|
|
||||||
static void AutocompleteScriptHttpCompletionCallback(HttpRequest& request,
|
|
||||||
HttpResponse& response, const AutocompleteScriptCompletionCallback& callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* APICLIENT_H */
|
|
@ -1,66 +0,0 @@
|
|||||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
|
||||||
|
|
||||||
#include "remote/httpchunkedencoding.hpp"
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
using namespace icinga;
|
|
||||||
|
|
||||||
StreamReadStatus HttpChunkedEncoding::ReadChunkFromStream(const Stream::Ptr& stream,
|
|
||||||
char **data, size_t *size, ChunkReadContext& context, bool may_wait)
|
|
||||||
{
|
|
||||||
if (context.LengthIndicator == -1) {
|
|
||||||
String line;
|
|
||||||
StreamReadStatus status = stream->ReadLine(&line, context.StreamContext, may_wait);
|
|
||||||
may_wait = false;
|
|
||||||
|
|
||||||
if (status != StatusNewItem)
|
|
||||||
return status;
|
|
||||||
|
|
||||||
std::stringstream msgbuf;
|
|
||||||
msgbuf << std::hex << line;
|
|
||||||
msgbuf >> context.LengthIndicator;
|
|
||||||
|
|
||||||
if (context.LengthIndicator < 0)
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("HTTP chunk length must not be negative."));
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamReadContext& scontext = context.StreamContext;
|
|
||||||
if (scontext.Eof)
|
|
||||||
return StatusEof;
|
|
||||||
|
|
||||||
if (scontext.MustRead) {
|
|
||||||
if (!scontext.FillFromStream(stream, may_wait)) {
|
|
||||||
scontext.Eof = true;
|
|
||||||
return StatusEof;
|
|
||||||
}
|
|
||||||
|
|
||||||
scontext.MustRead = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t NewlineLength = context.LengthIndicator ? 2 : 0;
|
|
||||||
|
|
||||||
if (scontext.Size < (size_t)context.LengthIndicator + NewlineLength) {
|
|
||||||
scontext.MustRead = true;
|
|
||||||
return StatusNeedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
*data = new char[context.LengthIndicator];
|
|
||||||
*size = context.LengthIndicator;
|
|
||||||
memcpy(*data, scontext.Buffer, context.LengthIndicator);
|
|
||||||
|
|
||||||
scontext.DropData(context.LengthIndicator + NewlineLength);
|
|
||||||
context.LengthIndicator = -1;
|
|
||||||
|
|
||||||
return StatusNewItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpChunkedEncoding::WriteChunkToStream(const Stream::Ptr& stream, const char *data, size_t count)
|
|
||||||
{
|
|
||||||
std::ostringstream msgbuf;
|
|
||||||
msgbuf << std::hex << count << "\r\n";
|
|
||||||
String lengthIndicator = msgbuf.str();
|
|
||||||
stream->Write(lengthIndicator.CStr(), lengthIndicator.GetLength());
|
|
||||||
stream->Write(data, count);
|
|
||||||
if (count > 0)
|
|
||||||
stream->Write("\r\n", 2);
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
|
||||||
|
|
||||||
#ifndef HTTPCHUNKEDENCODING_H
|
|
||||||
#define HTTPCHUNKEDENCODING_H
|
|
||||||
|
|
||||||
#include "remote/i2-remote.hpp"
|
|
||||||
#include "base/stream.hpp"
|
|
||||||
|
|
||||||
namespace icinga
|
|
||||||
{
|
|
||||||
|
|
||||||
struct ChunkReadContext
|
|
||||||
{
|
|
||||||
StreamReadContext& StreamContext;
|
|
||||||
int LengthIndicator;
|
|
||||||
|
|
||||||
ChunkReadContext(StreamReadContext& scontext)
|
|
||||||
: StreamContext(scontext), LengthIndicator(-1)
|
|
||||||
{ }
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HTTP chunked encoding.
|
|
||||||
*
|
|
||||||
* @ingroup remote
|
|
||||||
*/
|
|
||||||
struct HttpChunkedEncoding
|
|
||||||
{
|
|
||||||
static StreamReadStatus ReadChunkFromStream(const Stream::Ptr& stream,
|
|
||||||
char **data, size_t *size, ChunkReadContext& ccontext, bool may_wait = false);
|
|
||||||
static void WriteChunkToStream(const Stream::Ptr& stream, const char *data, size_t count);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* HTTPCHUNKEDENCODING_H */
|
|
@ -1,159 +0,0 @@
|
|||||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
|
||||||
|
|
||||||
#include "remote/httpclientconnection.hpp"
|
|
||||||
#include "base/configtype.hpp"
|
|
||||||
#include "base/objectlock.hpp"
|
|
||||||
#include "base/base64.hpp"
|
|
||||||
#include "base/utility.hpp"
|
|
||||||
#include "base/logger.hpp"
|
|
||||||
#include "base/exception.hpp"
|
|
||||||
#include "base/convert.hpp"
|
|
||||||
#include "base/tcpsocket.hpp"
|
|
||||||
#include "base/tlsstream.hpp"
|
|
||||||
#include "base/networkstream.hpp"
|
|
||||||
|
|
||||||
using namespace icinga;
|
|
||||||
|
|
||||||
HttpClientConnection::HttpClientConnection(String host, String port, bool tls)
|
|
||||||
: m_Host(std::move(host)), m_Port(std::move(port)), m_Tls(tls)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
void HttpClientConnection::Start()
|
|
||||||
{
|
|
||||||
/* Nothing to do here atm. */
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpClientConnection::Reconnect()
|
|
||||||
{
|
|
||||||
if (m_Stream)
|
|
||||||
m_Stream->Close();
|
|
||||||
|
|
||||||
m_Context.~StreamReadContext();
|
|
||||||
new (&m_Context) StreamReadContext();
|
|
||||||
|
|
||||||
m_Requests.clear();
|
|
||||||
m_CurrentResponse.reset();
|
|
||||||
|
|
||||||
TcpSocket::Ptr socket = new TcpSocket();
|
|
||||||
socket->Connect(m_Host, m_Port);
|
|
||||||
|
|
||||||
if (m_Tls)
|
|
||||||
m_Stream = new TlsStream(socket, m_Host, RoleClient);
|
|
||||||
else
|
|
||||||
ASSERT(!"Non-TLS HTTP connections not supported.");
|
|
||||||
/* m_Stream = new NetworkStream(socket);
|
|
||||||
* -- does not currently work because the NetworkStream class doesn't support async I/O
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* the stream holds an owning reference to this object through the callback we're registering here */
|
|
||||||
m_Stream->RegisterDataHandler(std::bind(&HttpClientConnection::DataAvailableHandler, HttpClientConnection::Ptr(this), _1));
|
|
||||||
if (m_Stream->IsDataAvailable())
|
|
||||||
DataAvailableHandler(m_Stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream::Ptr HttpClientConnection::GetStream() const
|
|
||||||
{
|
|
||||||
return m_Stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
String HttpClientConnection::GetHost() const
|
|
||||||
{
|
|
||||||
return m_Host;
|
|
||||||
}
|
|
||||||
|
|
||||||
String HttpClientConnection::GetPort() const
|
|
||||||
{
|
|
||||||
return m_Port;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HttpClientConnection::GetTls() const
|
|
||||||
{
|
|
||||||
return m_Tls;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpClientConnection::Disconnect()
|
|
||||||
{
|
|
||||||
Log(LogDebug, "HttpClientConnection", "Http client disconnected");
|
|
||||||
|
|
||||||
m_Stream->Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HttpClientConnection::ProcessMessage()
|
|
||||||
{
|
|
||||||
bool res;
|
|
||||||
|
|
||||||
if (m_Requests.empty()) {
|
|
||||||
m_Stream->Close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::pair<std::shared_ptr<HttpRequest>, HttpCompletionCallback>& currentRequest = *m_Requests.begin();
|
|
||||||
HttpRequest& request = *currentRequest.first.get();
|
|
||||||
const HttpCompletionCallback& callback = currentRequest.second;
|
|
||||||
|
|
||||||
if (!m_CurrentResponse)
|
|
||||||
m_CurrentResponse = std::make_shared<HttpResponse>(m_Stream, request);
|
|
||||||
|
|
||||||
std::shared_ptr<HttpResponse> currentResponse = m_CurrentResponse;
|
|
||||||
HttpResponse& response = *currentResponse.get();
|
|
||||||
|
|
||||||
try {
|
|
||||||
res = response.Parse(m_Context, false);
|
|
||||||
} catch (const std::exception&) {
|
|
||||||
callback(request, response);
|
|
||||||
|
|
||||||
m_Stream->Shutdown();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.Complete) {
|
|
||||||
callback(request, response);
|
|
||||||
|
|
||||||
m_Requests.pop_front();
|
|
||||||
m_CurrentResponse.reset();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpClientConnection::DataAvailableHandler(const Stream::Ptr& stream)
|
|
||||||
{
|
|
||||||
ASSERT(stream == m_Stream);
|
|
||||||
|
|
||||||
bool close = false;
|
|
||||||
|
|
||||||
if (!m_Stream->IsEof()) {
|
|
||||||
boost::mutex::scoped_lock lock(m_DataHandlerMutex);
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (ProcessMessage())
|
|
||||||
; /* empty loop body */
|
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
Log(LogWarning, "HttpClientConnection")
|
|
||||||
<< "Error while reading Http response: " << DiagnosticInformation(ex);
|
|
||||||
|
|
||||||
close = true;
|
|
||||||
Disconnect();
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
close = true;
|
|
||||||
|
|
||||||
if (close)
|
|
||||||
m_Stream->Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<HttpRequest> HttpClientConnection::NewRequest()
|
|
||||||
{
|
|
||||||
Reconnect();
|
|
||||||
return std::make_shared<HttpRequest>(m_Stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpClientConnection::SubmitRequest(const std::shared_ptr<HttpRequest>& request,
|
|
||||||
const HttpCompletionCallback& callback)
|
|
||||||
{
|
|
||||||
m_Requests.emplace_back(request, callback);
|
|
||||||
request->Finish();
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
|||||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
|
||||||
|
|
||||||
#ifndef HTTPCLIENTCONNECTION_H
|
|
||||||
#define HTTPCLIENTCONNECTION_H
|
|
||||||
|
|
||||||
#include "remote/httprequest.hpp"
|
|
||||||
#include "remote/httpresponse.hpp"
|
|
||||||
#include "base/stream.hpp"
|
|
||||||
#include "base/timer.hpp"
|
|
||||||
#include <deque>
|
|
||||||
|
|
||||||
namespace icinga
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An HTTP client connection.
|
|
||||||
*
|
|
||||||
* @ingroup remote
|
|
||||||
*/
|
|
||||||
class HttpClientConnection final : public Object
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DECLARE_PTR_TYPEDEFS(HttpClientConnection);
|
|
||||||
|
|
||||||
HttpClientConnection(String host, String port, bool tls = true);
|
|
||||||
|
|
||||||
void Start();
|
|
||||||
|
|
||||||
Stream::Ptr GetStream() const;
|
|
||||||
String GetHost() const;
|
|
||||||
String GetPort() const;
|
|
||||||
bool GetTls() const;
|
|
||||||
|
|
||||||
void Disconnect();
|
|
||||||
|
|
||||||
std::shared_ptr<HttpRequest> NewRequest();
|
|
||||||
|
|
||||||
typedef std::function<void(HttpRequest&, HttpResponse&)> HttpCompletionCallback;
|
|
||||||
void SubmitRequest(const std::shared_ptr<HttpRequest>& request, const HttpCompletionCallback& callback);
|
|
||||||
|
|
||||||
private:
|
|
||||||
String m_Host;
|
|
||||||
String m_Port;
|
|
||||||
bool m_Tls;
|
|
||||||
Stream::Ptr m_Stream;
|
|
||||||
std::deque<std::pair<std::shared_ptr<HttpRequest>, HttpCompletionCallback> > m_Requests;
|
|
||||||
std::shared_ptr<HttpResponse> m_CurrentResponse;
|
|
||||||
boost::mutex m_DataHandlerMutex;
|
|
||||||
|
|
||||||
StreamReadContext m_Context;
|
|
||||||
|
|
||||||
void Reconnect();
|
|
||||||
bool ProcessMessage();
|
|
||||||
void DataAvailableHandler(const Stream::Ptr& stream);
|
|
||||||
|
|
||||||
void ProcessMessageAsync(HttpRequest& request);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* HTTPCLIENTCONNECTION_H */
|
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
#include "remote/i2-remote.hpp"
|
#include "remote/i2-remote.hpp"
|
||||||
#include "remote/url.hpp"
|
#include "remote/url.hpp"
|
||||||
#include "remote/httpresponse.hpp"
|
|
||||||
#include "remote/httpserverconnection.hpp"
|
#include "remote/httpserverconnection.hpp"
|
||||||
#include "remote/apiuser.hpp"
|
#include "remote/apiuser.hpp"
|
||||||
#include "base/registry.hpp"
|
#include "base/registry.hpp"
|
||||||
|
@ -1,248 +0,0 @@
|
|||||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
|
||||||
|
|
||||||
#include "remote/httprequest.hpp"
|
|
||||||
#include "base/logger.hpp"
|
|
||||||
#include "base/application.hpp"
|
|
||||||
#include "base/convert.hpp"
|
|
||||||
|
|
||||||
using namespace icinga;
|
|
||||||
|
|
||||||
HttpRequest::HttpRequest(Stream::Ptr stream)
|
|
||||||
: CompleteHeaders(false),
|
|
||||||
CompleteHeaderCheck(false),
|
|
||||||
CompleteBody(false),
|
|
||||||
ProtocolVersion(HttpVersion11),
|
|
||||||
Headers(new Dictionary()),
|
|
||||||
m_Stream(std::move(stream)),
|
|
||||||
m_State(HttpRequestStart)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
bool HttpRequest::ParseHeaders(StreamReadContext& src, bool may_wait)
|
|
||||||
{
|
|
||||||
if (!m_Stream)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (m_State != HttpRequestStart && m_State != HttpRequestHeaders)
|
|
||||||
BOOST_THROW_EXCEPTION(std::runtime_error("Invalid HTTP state"));
|
|
||||||
|
|
||||||
String line;
|
|
||||||
StreamReadStatus srs = m_Stream->ReadLine(&line, src, may_wait);
|
|
||||||
|
|
||||||
if (srs != StatusNewItem) {
|
|
||||||
if (src.Size > 8 * 1024)
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Line length for HTTP header exceeded"));
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.GetLength() > 8 * 1024) {
|
|
||||||
#ifdef I2_DEBUG /* I2_DEBUG */
|
|
||||||
Log(LogDebug, "HttpRequest")
|
|
||||||
<< "Header size: " << line.GetLength() << " content: '" << line << "'.";
|
|
||||||
#endif /* I2_DEBUG */
|
|
||||||
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Line length for HTTP header exceeded"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_State == HttpRequestStart) {
|
|
||||||
/* ignore trailing new-lines */
|
|
||||||
if (line == "")
|
|
||||||
return true;
|
|
||||||
|
|
||||||
std::vector<String> tokens = line.Split(" ");
|
|
||||||
Log(LogDebug, "HttpRequest")
|
|
||||||
<< "line: " << line << ", tokens: " << tokens.size();
|
|
||||||
if (tokens.size() != 3)
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
|
|
||||||
|
|
||||||
RequestMethod = tokens[0];
|
|
||||||
RequestUrl = new class Url(tokens[1]);
|
|
||||||
|
|
||||||
if (tokens[2] == "HTTP/1.0")
|
|
||||||
ProtocolVersion = HttpVersion10;
|
|
||||||
else if (tokens[2] == "HTTP/1.1") {
|
|
||||||
ProtocolVersion = HttpVersion11;
|
|
||||||
} else
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version"));
|
|
||||||
|
|
||||||
m_State = HttpRequestHeaders;
|
|
||||||
return true;
|
|
||||||
} else { // m_State = HttpRequestHeaders
|
|
||||||
if (line == "") {
|
|
||||||
m_State = HttpRequestBody;
|
|
||||||
CompleteHeaders = true;
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (Headers->GetLength() > 128)
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Maximum number of HTTP request headers exceeded"));
|
|
||||||
|
|
||||||
String::SizeType pos = line.FindFirstOf(":");
|
|
||||||
if (pos == String::NPos)
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
|
|
||||||
|
|
||||||
String key = line.SubStr(0, pos).ToLower().Trim();
|
|
||||||
String value = line.SubStr(pos + 1).Trim();
|
|
||||||
Headers->Set(key, value);
|
|
||||||
|
|
||||||
if (key == "x-http-method-override")
|
|
||||||
RequestMethod = value;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HttpRequest::ParseBody(StreamReadContext& src, bool may_wait)
|
|
||||||
{
|
|
||||||
if (!m_Stream)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (m_State != HttpRequestBody)
|
|
||||||
BOOST_THROW_EXCEPTION(std::runtime_error("Invalid HTTP state"));
|
|
||||||
|
|
||||||
/* we're done if the request doesn't contain a message body */
|
|
||||||
if (!Headers->Contains("content-length") && !Headers->Contains("transfer-encoding")) {
|
|
||||||
CompleteBody = true;
|
|
||||||
return true;
|
|
||||||
} else if (!m_Body)
|
|
||||||
m_Body = new FIFO();
|
|
||||||
|
|
||||||
if (Headers->Get("transfer-encoding") == "chunked") {
|
|
||||||
if (!m_ChunkContext)
|
|
||||||
m_ChunkContext = std::make_shared<ChunkReadContext>(std::ref(src));
|
|
||||||
|
|
||||||
char *data;
|
|
||||||
size_t size;
|
|
||||||
StreamReadStatus srs = HttpChunkedEncoding::ReadChunkFromStream(m_Stream, &data, &size, *m_ChunkContext.get(), may_wait);
|
|
||||||
|
|
||||||
if (srs != StatusNewItem)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
m_Body->Write(data, size);
|
|
||||||
|
|
||||||
delete [] data;
|
|
||||||
|
|
||||||
if (size == 0) {
|
|
||||||
CompleteBody = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (src.Eof)
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
|
|
||||||
|
|
||||||
if (src.MustRead) {
|
|
||||||
if (!src.FillFromStream(m_Stream, false)) {
|
|
||||||
src.Eof = true;
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
|
|
||||||
}
|
|
||||||
|
|
||||||
src.MustRead = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
long length_indicator_signed = Convert::ToLong(Headers->Get("content-length"));
|
|
||||||
|
|
||||||
if (length_indicator_signed < 0)
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Content-Length must not be negative."));
|
|
||||||
|
|
||||||
size_t length_indicator = length_indicator_signed;
|
|
||||||
|
|
||||||
if (src.Size < length_indicator) {
|
|
||||||
src.MustRead = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Body->Write(src.Buffer, length_indicator);
|
|
||||||
src.DropData(length_indicator);
|
|
||||||
CompleteBody = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t HttpRequest::ReadBody(char *data, size_t count)
|
|
||||||
{
|
|
||||||
if (!m_Body)
|
|
||||||
return 0;
|
|
||||||
else
|
|
||||||
return m_Body->Read(data, count, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpRequest::AddHeader(const String& key, const String& value)
|
|
||||||
{
|
|
||||||
ASSERT(m_State == HttpRequestStart || m_State == HttpRequestHeaders);
|
|
||||||
Headers->Set(key.ToLower(), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpRequest::FinishHeaders()
|
|
||||||
{
|
|
||||||
if (m_State == HttpRequestStart) {
|
|
||||||
String rqline = RequestMethod + " " + RequestUrl->Format(true) + " HTTP/1." + (ProtocolVersion == HttpVersion10 ? "0" : "1") + "\r\n";
|
|
||||||
m_Stream->Write(rqline.CStr(), rqline.GetLength());
|
|
||||||
m_State = HttpRequestHeaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_State == HttpRequestHeaders) {
|
|
||||||
AddHeader("User-Agent", "Icinga/" + Application::GetAppVersion());
|
|
||||||
|
|
||||||
if (ProtocolVersion == HttpVersion11) {
|
|
||||||
AddHeader("Transfer-Encoding", "chunked");
|
|
||||||
if (!Headers->Contains("Host"))
|
|
||||||
AddHeader("Host", RequestUrl->GetHost() + ":" + RequestUrl->GetPort());
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectLock olock(Headers);
|
|
||||||
for (const Dictionary::Pair& kv : Headers)
|
|
||||||
{
|
|
||||||
String header = kv.first + ": " + kv.second + "\r\n";
|
|
||||||
m_Stream->Write(header.CStr(), header.GetLength());
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Stream->Write("\r\n", 2);
|
|
||||||
|
|
||||||
m_State = HttpRequestBody;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpRequest::WriteBody(const char *data, size_t count)
|
|
||||||
{
|
|
||||||
ASSERT(m_State == HttpRequestStart || m_State == HttpRequestHeaders || m_State == HttpRequestBody);
|
|
||||||
|
|
||||||
if (ProtocolVersion == HttpVersion10) {
|
|
||||||
if (!m_Body)
|
|
||||||
m_Body = new FIFO();
|
|
||||||
|
|
||||||
m_Body->Write(data, count);
|
|
||||||
} else {
|
|
||||||
FinishHeaders();
|
|
||||||
|
|
||||||
HttpChunkedEncoding::WriteChunkToStream(m_Stream, data, count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpRequest::Finish()
|
|
||||||
{
|
|
||||||
ASSERT(m_State != HttpRequestEnd);
|
|
||||||
|
|
||||||
if (ProtocolVersion == HttpVersion10) {
|
|
||||||
if (m_Body)
|
|
||||||
AddHeader("Content-Length", Convert::ToString(m_Body->GetAvailableBytes()));
|
|
||||||
|
|
||||||
FinishHeaders();
|
|
||||||
|
|
||||||
while (m_Body && m_Body->IsDataAvailable()) {
|
|
||||||
char buffer[1024];
|
|
||||||
size_t rc = m_Body->Read(buffer, sizeof(buffer), true);
|
|
||||||
m_Stream->Write(buffer, rc);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (m_State == HttpRequestStart || m_State == HttpRequestHeaders)
|
|
||||||
FinishHeaders();
|
|
||||||
|
|
||||||
WriteBody(nullptr, 0);
|
|
||||||
m_Stream->Write("\r\n", 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_State = HttpRequestEnd;
|
|
||||||
}
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
|||||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
|
||||||
|
|
||||||
#ifndef HTTPREQUEST_H
|
|
||||||
#define HTTPREQUEST_H
|
|
||||||
|
|
||||||
#include "remote/i2-remote.hpp"
|
|
||||||
#include "remote/httpchunkedencoding.hpp"
|
|
||||||
#include "remote/url.hpp"
|
|
||||||
#include "base/stream.hpp"
|
|
||||||
#include "base/fifo.hpp"
|
|
||||||
#include "base/dictionary.hpp"
|
|
||||||
|
|
||||||
namespace icinga
|
|
||||||
{
|
|
||||||
|
|
||||||
enum HttpVersion
|
|
||||||
{
|
|
||||||
HttpVersion10,
|
|
||||||
HttpVersion11
|
|
||||||
};
|
|
||||||
|
|
||||||
enum HttpRequestState
|
|
||||||
{
|
|
||||||
HttpRequestStart,
|
|
||||||
HttpRequestHeaders,
|
|
||||||
HttpRequestBody,
|
|
||||||
HttpRequestEnd
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An HTTP request.
|
|
||||||
*
|
|
||||||
* @ingroup remote
|
|
||||||
*/
|
|
||||||
struct HttpRequest
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
bool CompleteHeaders;
|
|
||||||
bool CompleteHeaderCheck;
|
|
||||||
bool CompleteBody;
|
|
||||||
|
|
||||||
String RequestMethod;
|
|
||||||
Url::Ptr RequestUrl;
|
|
||||||
HttpVersion ProtocolVersion;
|
|
||||||
|
|
||||||
Dictionary::Ptr Headers;
|
|
||||||
|
|
||||||
HttpRequest(Stream::Ptr stream);
|
|
||||||
|
|
||||||
bool ParseHeaders(StreamReadContext& src, bool may_wait);
|
|
||||||
bool ParseBody(StreamReadContext& src, bool may_wait);
|
|
||||||
size_t ReadBody(char *data, size_t count);
|
|
||||||
|
|
||||||
void AddHeader(const String& key, const String& value);
|
|
||||||
void WriteBody(const char *data, size_t count);
|
|
||||||
void Finish();
|
|
||||||
|
|
||||||
private:
|
|
||||||
Stream::Ptr m_Stream;
|
|
||||||
std::shared_ptr<ChunkReadContext> m_ChunkContext;
|
|
||||||
HttpRequestState m_State;
|
|
||||||
FIFO::Ptr m_Body;
|
|
||||||
|
|
||||||
void FinishHeaders();
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* HTTPREQUEST_H */
|
|
@ -1,259 +0,0 @@
|
|||||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
|
||||||
|
|
||||||
#include "remote/httpresponse.hpp"
|
|
||||||
#include "remote/httpchunkedencoding.hpp"
|
|
||||||
#include "base/logger.hpp"
|
|
||||||
#include "base/application.hpp"
|
|
||||||
#include "base/convert.hpp"
|
|
||||||
|
|
||||||
using namespace icinga;
|
|
||||||
|
|
||||||
HttpResponse::HttpResponse(Stream::Ptr stream, const HttpRequest& request)
|
|
||||||
: Complete(false), m_State(HttpResponseStart), m_Request(&request), m_Stream(std::move(stream))
|
|
||||||
{ }
|
|
||||||
|
|
||||||
void HttpResponse::SetStatus(int code, const String& message)
|
|
||||||
{
|
|
||||||
ASSERT(code >= 100 && code <= 599);
|
|
||||||
ASSERT(!message.IsEmpty());
|
|
||||||
|
|
||||||
if (m_State != HttpResponseStart) {
|
|
||||||
Log(LogWarning, "HttpResponse", "Tried to set Http response status after headers had already been sent.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String status = "HTTP/";
|
|
||||||
|
|
||||||
if (m_Request->ProtocolVersion == HttpVersion10)
|
|
||||||
status += "1.0";
|
|
||||||
else
|
|
||||||
status += "1.1";
|
|
||||||
|
|
||||||
status += " " + Convert::ToString(code) + " " + message + "\r\n";
|
|
||||||
|
|
||||||
m_Stream->Write(status.CStr(), status.GetLength());
|
|
||||||
|
|
||||||
m_State = HttpResponseHeaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpResponse::AddHeader(const String& key, const String& value)
|
|
||||||
{
|
|
||||||
m_Headers.emplace_back(key + ": " + value + "\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpResponse::FinishHeaders()
|
|
||||||
{
|
|
||||||
if (m_State == HttpResponseHeaders) {
|
|
||||||
if (m_Request->ProtocolVersion == HttpVersion11)
|
|
||||||
AddHeader("Transfer-Encoding", "chunked");
|
|
||||||
|
|
||||||
AddHeader("Server", "Icinga/" + Application::GetAppVersion());
|
|
||||||
|
|
||||||
for (const String& header : m_Headers)
|
|
||||||
m_Stream->Write(header.CStr(), header.GetLength());
|
|
||||||
|
|
||||||
m_Stream->Write("\r\n", 2);
|
|
||||||
m_State = HttpResponseBody;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpResponse::WriteBody(const char *data, size_t count)
|
|
||||||
{
|
|
||||||
ASSERT(m_State == HttpResponseHeaders || m_State == HttpResponseBody);
|
|
||||||
|
|
||||||
if (m_Request->ProtocolVersion == HttpVersion10) {
|
|
||||||
if (!m_Body)
|
|
||||||
m_Body = new FIFO();
|
|
||||||
|
|
||||||
m_Body->Write(data, count);
|
|
||||||
} else {
|
|
||||||
FinishHeaders();
|
|
||||||
|
|
||||||
HttpChunkedEncoding::WriteChunkToStream(m_Stream, data, count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpResponse::Finish()
|
|
||||||
{
|
|
||||||
ASSERT(m_State != HttpResponseEnd);
|
|
||||||
|
|
||||||
if (m_Request->ProtocolVersion == HttpVersion10) {
|
|
||||||
if (m_Body)
|
|
||||||
AddHeader("Content-Length", Convert::ToString(m_Body->GetAvailableBytes()));
|
|
||||||
|
|
||||||
FinishHeaders();
|
|
||||||
|
|
||||||
while (m_Body && m_Body->IsDataAvailable()) {
|
|
||||||
char buffer[1024];
|
|
||||||
size_t rc = m_Body->Read(buffer, sizeof(buffer), true);
|
|
||||||
m_Stream->Write(buffer, rc);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
WriteBody(nullptr, 0);
|
|
||||||
m_Stream->Write("\r\n", 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_State = HttpResponseEnd;
|
|
||||||
|
|
||||||
/* Close the connection on
|
|
||||||
* a) HTTP/1.0
|
|
||||||
* b) Connection: close in the sent header.
|
|
||||||
*
|
|
||||||
* Do this here and not in DataAvailableHandler - there might still be incoming data in there.
|
|
||||||
*/
|
|
||||||
if (m_Request->ProtocolVersion == HttpVersion10 || m_Request->Headers->Get("connection") == "close")
|
|
||||||
m_Stream->Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HttpResponse::Parse(StreamReadContext& src, bool may_wait)
|
|
||||||
{
|
|
||||||
if (m_State != HttpResponseBody) {
|
|
||||||
String line;
|
|
||||||
StreamReadStatus srs = m_Stream->ReadLine(&line, src, may_wait);
|
|
||||||
|
|
||||||
if (srs != StatusNewItem)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (m_State == HttpResponseStart) {
|
|
||||||
/* ignore trailing new-lines */
|
|
||||||
if (line == "")
|
|
||||||
return true;
|
|
||||||
|
|
||||||
std::vector<String> tokens = line.Split(" ");
|
|
||||||
Log(LogDebug, "HttpRequest")
|
|
||||||
<< "line: " << line << ", tokens: " << tokens.size();
|
|
||||||
if (tokens.size() < 2)
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP response (Status line)"));
|
|
||||||
|
|
||||||
if (tokens[0] == "HTTP/1.0")
|
|
||||||
ProtocolVersion = HttpVersion10;
|
|
||||||
else if (tokens[0] == "HTTP/1.1") {
|
|
||||||
ProtocolVersion = HttpVersion11;
|
|
||||||
} else
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version"));
|
|
||||||
|
|
||||||
StatusCode = Convert::ToLong(tokens[1]);
|
|
||||||
|
|
||||||
if (tokens.size() >= 3)
|
|
||||||
StatusMessage = tokens[2]; // TODO: Join tokens[2..end]
|
|
||||||
|
|
||||||
m_State = HttpResponseHeaders;
|
|
||||||
} else if (m_State == HttpResponseHeaders) {
|
|
||||||
if (!Headers)
|
|
||||||
Headers = new Dictionary();
|
|
||||||
|
|
||||||
if (line == "") {
|
|
||||||
m_State = HttpResponseBody;
|
|
||||||
m_Body = new FIFO();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
String::SizeType pos = line.FindFirstOf(":");
|
|
||||||
if (pos == String::NPos)
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
|
|
||||||
String key = line.SubStr(0, pos).ToLower().Trim();
|
|
||||||
|
|
||||||
String value = line.SubStr(pos + 1).Trim();
|
|
||||||
Headers->Set(key, value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
VERIFY(!"Invalid HTTP request state.");
|
|
||||||
}
|
|
||||||
} else if (m_State == HttpResponseBody) {
|
|
||||||
if (Headers->Get("transfer-encoding") == "chunked") {
|
|
||||||
if (!m_ChunkContext)
|
|
||||||
m_ChunkContext = std::make_shared<ChunkReadContext>(std::ref(src));
|
|
||||||
|
|
||||||
char *data;
|
|
||||||
size_t size;
|
|
||||||
StreamReadStatus srs = HttpChunkedEncoding::ReadChunkFromStream(m_Stream, &data, &size, *m_ChunkContext.get(), may_wait);
|
|
||||||
|
|
||||||
if (srs != StatusNewItem)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
Log(LogNotice, "HttpResponse")
|
|
||||||
<< "Read " << size << " bytes";
|
|
||||||
|
|
||||||
m_Body->Write(data, size);
|
|
||||||
|
|
||||||
delete[] data;
|
|
||||||
|
|
||||||
if (size == 0) {
|
|
||||||
Complete = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bool hasLengthIndicator = false;
|
|
||||||
size_t lengthIndicator = 0;
|
|
||||||
Value contentLengthHeader;
|
|
||||||
|
|
||||||
if (Headers->Get("content-length", &contentLengthHeader)) {
|
|
||||||
hasLengthIndicator = true;
|
|
||||||
lengthIndicator = Convert::ToLong(contentLengthHeader);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasLengthIndicator && ProtocolVersion != HttpVersion10 && !Headers->Contains("transfer-encoding")) {
|
|
||||||
Complete = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasLengthIndicator && src.Eof)
|
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
|
|
||||||
|
|
||||||
if (src.MustRead) {
|
|
||||||
if (!src.FillFromStream(m_Stream, may_wait))
|
|
||||||
src.Eof = true;
|
|
||||||
|
|
||||||
src.MustRead = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasLengthIndicator)
|
|
||||||
lengthIndicator = src.Size;
|
|
||||||
|
|
||||||
if (src.Size < lengthIndicator) {
|
|
||||||
src.MustRead = true;
|
|
||||||
return may_wait;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Body->Write(src.Buffer, lengthIndicator);
|
|
||||||
src.DropData(lengthIndicator);
|
|
||||||
|
|
||||||
if (!hasLengthIndicator && !src.Eof) {
|
|
||||||
src.MustRead = true;
|
|
||||||
return may_wait;
|
|
||||||
}
|
|
||||||
|
|
||||||
Complete = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t HttpResponse::ReadBody(char *data, size_t count)
|
|
||||||
{
|
|
||||||
if (!m_Body)
|
|
||||||
return 0;
|
|
||||||
else
|
|
||||||
return m_Body->Read(data, count, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t HttpResponse::GetBodySize() const
|
|
||||||
{
|
|
||||||
if (!m_Body)
|
|
||||||
return 0;
|
|
||||||
else
|
|
||||||
return m_Body->GetAvailableBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HttpResponse::IsPeerConnected() const
|
|
||||||
{
|
|
||||||
return !m_Stream->IsEof();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpResponse::RebindRequest(const HttpRequest& request)
|
|
||||||
{
|
|
||||||
m_Request = &request;
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
|
||||||
|
|
||||||
#ifndef HTTPRESPONSE_H
|
|
||||||
#define HTTPRESPONSE_H
|
|
||||||
|
|
||||||
#include "remote/httprequest.hpp"
|
|
||||||
#include "base/stream.hpp"
|
|
||||||
#include "base/fifo.hpp"
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace icinga
|
|
||||||
{
|
|
||||||
|
|
||||||
enum HttpResponseState
|
|
||||||
{
|
|
||||||
HttpResponseStart,
|
|
||||||
HttpResponseHeaders,
|
|
||||||
HttpResponseBody,
|
|
||||||
HttpResponseEnd
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An HTTP response.
|
|
||||||
*
|
|
||||||
* @ingroup remote
|
|
||||||
*/
|
|
||||||
struct HttpResponse
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
bool Complete;
|
|
||||||
|
|
||||||
HttpVersion ProtocolVersion;
|
|
||||||
int StatusCode;
|
|
||||||
String StatusMessage;
|
|
||||||
|
|
||||||
Dictionary::Ptr Headers;
|
|
||||||
|
|
||||||
HttpResponse(Stream::Ptr stream, const HttpRequest& request);
|
|
||||||
|
|
||||||
bool Parse(StreamReadContext& src, bool may_wait);
|
|
||||||
size_t ReadBody(char *data, size_t count);
|
|
||||||
size_t GetBodySize() const;
|
|
||||||
|
|
||||||
void SetStatus(int code, const String& message);
|
|
||||||
void AddHeader(const String& key, const String& value);
|
|
||||||
void WriteBody(const char *data, size_t count);
|
|
||||||
void Finish();
|
|
||||||
|
|
||||||
bool IsPeerConnected() const;
|
|
||||||
|
|
||||||
void RebindRequest(const HttpRequest& request);
|
|
||||||
|
|
||||||
private:
|
|
||||||
HttpResponseState m_State;
|
|
||||||
std::shared_ptr<ChunkReadContext> m_ChunkContext;
|
|
||||||
const HttpRequest *m_Request;
|
|
||||||
Stream::Ptr m_Stream;
|
|
||||||
FIFO::Ptr m_Body;
|
|
||||||
std::vector<String> m_Headers;
|
|
||||||
|
|
||||||
void FinishHeaders();
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* HTTPRESPONSE_H */
|
|
@ -37,29 +37,6 @@ Dictionary::Ptr HttpUtility::FetchRequestParameters(const Url::Ptr& url, const s
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpUtility::SendJsonBody(HttpResponse& response, const Dictionary::Ptr& params, const Value& val)
|
|
||||||
{
|
|
||||||
response.AddHeader("Content-Type", "application/json");
|
|
||||||
|
|
||||||
bool prettyPrint = false;
|
|
||||||
|
|
||||||
if (params)
|
|
||||||
prettyPrint = GetLastParameter(params, "pretty");
|
|
||||||
|
|
||||||
String body = JsonEncode(val, prettyPrint);
|
|
||||||
|
|
||||||
response.WriteBody(body.CStr(), body.GetLength());
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpUtility::SendJsonBody(boost::beast::http::response<boost::beast::http::string_body>& response, const Dictionary::Ptr& params, const Value& val)
|
|
||||||
{
|
|
||||||
namespace http = boost::beast::http;
|
|
||||||
|
|
||||||
response.set(http::field::content_type, "application/json");
|
|
||||||
response.body() = JsonEncode(val, params && GetLastParameter(params, "pretty"));
|
|
||||||
response.set(http::field::content_length, response.body().size());
|
|
||||||
}
|
|
||||||
|
|
||||||
Value HttpUtility::GetLastParameter(const Dictionary::Ptr& params, const String& key)
|
Value HttpUtility::GetLastParameter(const Dictionary::Ptr& params, const String& key)
|
||||||
{
|
{
|
||||||
Value varr = params->Get(key);
|
Value varr = params->Get(key);
|
||||||
@ -75,27 +52,13 @@ Value HttpUtility::GetLastParameter(const Dictionary::Ptr& params, const String&
|
|||||||
return arr->Get(arr->GetLength() - 1);
|
return arr->Get(arr->GetLength() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpUtility::SendJsonError(HttpResponse& response, const Dictionary::Ptr& params,
|
void HttpUtility::SendJsonBody(boost::beast::http::response<boost::beast::http::string_body>& response, const Dictionary::Ptr& params, const Value& val)
|
||||||
int code, const String& info, const String& diagnosticInformation)
|
|
||||||
{
|
{
|
||||||
Dictionary::Ptr result = new Dictionary();
|
namespace http = boost::beast::http;
|
||||||
response.SetStatus(code, HttpUtility::GetErrorNameByCode(code));
|
|
||||||
result->Set("error", code);
|
|
||||||
|
|
||||||
bool verbose = false;
|
response.set(http::field::content_type, "application/json");
|
||||||
|
response.body() = JsonEncode(val, params && GetLastParameter(params, "pretty"));
|
||||||
if (params)
|
response.set(http::field::content_length, response.body().size());
|
||||||
verbose = HttpUtility::GetLastParameter(params, "verbose");
|
|
||||||
|
|
||||||
if (!info.IsEmpty())
|
|
||||||
result->Set("status", info);
|
|
||||||
|
|
||||||
if (verbose) {
|
|
||||||
if (!diagnosticInformation.IsEmpty())
|
|
||||||
result->Set("diagnostic_information", diagnosticInformation);
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpUtility::SendJsonBody(response, params, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpUtility::SendJsonError(boost::beast::http::response<boost::beast::http::string_body>& response,
|
void HttpUtility::SendJsonError(boost::beast::http::response<boost::beast::http::string_body>& response,
|
||||||
@ -115,32 +78,3 @@ void HttpUtility::SendJsonError(boost::beast::http::response<boost::beast::http:
|
|||||||
|
|
||||||
HttpUtility::SendJsonBody(response, params, result);
|
HttpUtility::SendJsonBody(response, params, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
String HttpUtility::GetErrorNameByCode(const int code)
|
|
||||||
{
|
|
||||||
switch(code) {
|
|
||||||
case 200:
|
|
||||||
return "OK";
|
|
||||||
case 201:
|
|
||||||
return "Created";
|
|
||||||
case 204:
|
|
||||||
return "No Content";
|
|
||||||
case 304:
|
|
||||||
return "Not Modified";
|
|
||||||
case 400:
|
|
||||||
return "Bad Request";
|
|
||||||
case 401:
|
|
||||||
return "Unauthorized";
|
|
||||||
case 403:
|
|
||||||
return "Forbidden";
|
|
||||||
case 404:
|
|
||||||
return "Not Found";
|
|
||||||
case 409:
|
|
||||||
return "Conflict";
|
|
||||||
case 500:
|
|
||||||
return "Internal Server Error";
|
|
||||||
default:
|
|
||||||
return "Unknown Error Code";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -3,12 +3,10 @@
|
|||||||
#ifndef HTTPUTILITY_H
|
#ifndef HTTPUTILITY_H
|
||||||
#define HTTPUTILITY_H
|
#define HTTPUTILITY_H
|
||||||
|
|
||||||
#include "remote/httprequest.hpp"
|
|
||||||
#include "remote/httpresponse.hpp"
|
|
||||||
#include "remote/url.hpp"
|
#include "remote/url.hpp"
|
||||||
#include "base/dictionary.hpp"
|
#include "base/dictionary.hpp"
|
||||||
#include <string>
|
|
||||||
#include <boost/beast/http.hpp>
|
#include <boost/beast/http.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace icinga
|
namespace icinga
|
||||||
{
|
{
|
||||||
@ -23,17 +21,11 @@ class HttpUtility
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
static Dictionary::Ptr FetchRequestParameters(const Url::Ptr& url, const std::string& body);
|
static Dictionary::Ptr FetchRequestParameters(const Url::Ptr& url, const std::string& body);
|
||||||
static void SendJsonBody(HttpResponse& response, const Dictionary::Ptr& params, const Value& val);
|
|
||||||
static void SendJsonBody(boost::beast::http::response<boost::beast::http::string_body>& response, const Dictionary::Ptr& params, const Value& val);
|
|
||||||
static Value GetLastParameter(const Dictionary::Ptr& params, const String& key);
|
static Value GetLastParameter(const Dictionary::Ptr& params, const String& key);
|
||||||
static void SendJsonError(HttpResponse& response, const Dictionary::Ptr& params, const int code,
|
|
||||||
const String& verbose = String(), const String& diagnosticInformation = String());
|
static void SendJsonBody(boost::beast::http::response<boost::beast::http::string_body>& response, const Dictionary::Ptr& params, const Value& val);
|
||||||
static void SendJsonError(boost::beast::http::response<boost::beast::http::string_body>& response, const Dictionary::Ptr& params, const int code,
|
static void SendJsonError(boost::beast::http::response<boost::beast::http::string_body>& response, const Dictionary::Ptr& params, const int code,
|
||||||
const String& verbose = String(), const String& diagnosticInformation = String());
|
const String& verbose = String(), const String& diagnosticInformation = String());
|
||||||
|
|
||||||
private:
|
|
||||||
static String GetErrorNameByCode(int code);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,11 @@
|
|||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
|
|
||||||
#ifdef I2_DEBUG
|
#ifdef I2_DEBUG
|
||||||
|
/**
|
||||||
|
* Determine whether the developer wants to see raw JSON messages.
|
||||||
|
*
|
||||||
|
* @return Internal.DebugJsonRpc boolean
|
||||||
|
*/
|
||||||
static bool GetDebugJsonRpcCached()
|
static bool GetDebugJsonRpcCached()
|
||||||
{
|
{
|
||||||
static int debugJsonRpc = -1;
|
static int debugJsonRpc = -1;
|
||||||
@ -40,25 +45,6 @@ static bool GetDebugJsonRpcCached()
|
|||||||
}
|
}
|
||||||
#endif /* I2_DEBUG */
|
#endif /* I2_DEBUG */
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a message to the connected peer and returns the bytes sent.
|
|
||||||
*
|
|
||||||
* @param message The message.
|
|
||||||
*
|
|
||||||
* @return The amount of bytes sent.
|
|
||||||
*/
|
|
||||||
size_t JsonRpc::SendMessage(const Stream::Ptr& stream, const Dictionary::Ptr& message)
|
|
||||||
{
|
|
||||||
String json = JsonEncode(message);
|
|
||||||
|
|
||||||
#ifdef I2_DEBUG
|
|
||||||
if (GetDebugJsonRpcCached())
|
|
||||||
std::cerr << ConsoleColorTag(Console_ForegroundBlue) << ">> " << json << ConsoleColorTag(Console_Normal) << "\n";
|
|
||||||
#endif /* I2_DEBUG */
|
|
||||||
|
|
||||||
return NetString::WriteStringToStream(stream, json);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a message to the connected peer and returns the bytes sent.
|
* Sends a message to the connected peer and returns the bytes sent.
|
||||||
*
|
*
|
||||||
@ -91,11 +77,13 @@ size_t JsonRpc::SendMessage(const std::shared_ptr<AsioTlsStream>& stream, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a message to the connected peer and returns the bytes sent.
|
* Sends a raw message to the connected peer.
|
||||||
*
|
*
|
||||||
* @param message The message.
|
* @param stream ASIO TLS Stream
|
||||||
|
* @param json message
|
||||||
|
* @param yc Yield context required for ASIO
|
||||||
*
|
*
|
||||||
* @return The amount of bytes sent.
|
* @return bytes sent
|
||||||
*/
|
*/
|
||||||
size_t JsonRpc::SendRawMessage(const std::shared_ptr<AsioTlsStream>& stream, const String& json, boost::asio::yield_context yc)
|
size_t JsonRpc::SendRawMessage(const std::shared_ptr<AsioTlsStream>& stream, const String& json, boost::asio::yield_context yc)
|
||||||
{
|
{
|
||||||
@ -107,23 +95,14 @@ size_t JsonRpc::SendRawMessage(const std::shared_ptr<AsioTlsStream>& stream, con
|
|||||||
return NetString::WriteStringToStream(stream, json, yc);
|
return NetString::WriteStringToStream(stream, json, yc);
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamReadStatus JsonRpc::ReadMessage(const Stream::Ptr& stream, String *message, StreamReadContext& src, bool may_wait, ssize_t maxMessageLength)
|
/**
|
||||||
{
|
* Reads a message from the connected peer.
|
||||||
String jsonString;
|
*
|
||||||
StreamReadStatus srs = NetString::ReadStringFromStream(stream, &jsonString, src, may_wait, maxMessageLength);
|
* @param stream ASIO TLS Stream
|
||||||
|
* @param maxMessageLength maximum size of bytes read.
|
||||||
if (srs != StatusNewItem)
|
*
|
||||||
return srs;
|
* @return A JSON string
|
||||||
|
*/
|
||||||
*message = jsonString;
|
|
||||||
|
|
||||||
#ifdef I2_DEBUG
|
|
||||||
if (GetDebugJsonRpcCached())
|
|
||||||
std::cerr << ConsoleColorTag(Console_ForegroundBlue) << "<< " << jsonString << ConsoleColorTag(Console_Normal) << "\n";
|
|
||||||
#endif /* I2_DEBUG */
|
|
||||||
|
|
||||||
return StatusNewItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
String JsonRpc::ReadMessage(const std::shared_ptr<AsioTlsStream>& stream, ssize_t maxMessageLength)
|
String JsonRpc::ReadMessage(const std::shared_ptr<AsioTlsStream>& stream, ssize_t maxMessageLength)
|
||||||
{
|
{
|
||||||
@ -137,6 +116,15 @@ String JsonRpc::ReadMessage(const std::shared_ptr<AsioTlsStream>& stream, ssize_
|
|||||||
return std::move(jsonString);
|
return std::move(jsonString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a message from the connected peer.
|
||||||
|
*
|
||||||
|
* @param stream ASIO TLS Stream
|
||||||
|
* @param yc Yield Context for ASIO
|
||||||
|
* @param maxMessageLength maximum size of bytes read.
|
||||||
|
*
|
||||||
|
* @return A JSON string
|
||||||
|
*/
|
||||||
String JsonRpc::ReadMessage(const std::shared_ptr<AsioTlsStream>& stream, boost::asio::yield_context yc, ssize_t maxMessageLength)
|
String JsonRpc::ReadMessage(const std::shared_ptr<AsioTlsStream>& stream, boost::asio::yield_context yc, ssize_t maxMessageLength)
|
||||||
{
|
{
|
||||||
String jsonString = NetString::ReadStringFromStream(stream, yc, maxMessageLength);
|
String jsonString = NetString::ReadStringFromStream(stream, yc, maxMessageLength);
|
||||||
@ -149,6 +137,13 @@ String JsonRpc::ReadMessage(const std::shared_ptr<AsioTlsStream>& stream, boost:
|
|||||||
return std::move(jsonString);
|
return std::move(jsonString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode message, enforce a Dictionary
|
||||||
|
*
|
||||||
|
* @param message JSON string
|
||||||
|
*
|
||||||
|
* @return Dictionary ptr
|
||||||
|
*/
|
||||||
Dictionary::Ptr JsonRpc::DecodeMessage(const String& message)
|
Dictionary::Ptr JsonRpc::DecodeMessage(const String& message)
|
||||||
{
|
{
|
||||||
Value value = JsonDecode(message);
|
Value value = JsonDecode(message);
|
||||||
|
@ -21,13 +21,13 @@ namespace icinga
|
|||||||
class JsonRpc
|
class JsonRpc
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static size_t SendMessage(const Stream::Ptr& stream, const Dictionary::Ptr& message);
|
|
||||||
static size_t SendMessage(const std::shared_ptr<AsioTlsStream>& stream, const Dictionary::Ptr& message);
|
static size_t SendMessage(const std::shared_ptr<AsioTlsStream>& stream, const Dictionary::Ptr& message);
|
||||||
static size_t SendMessage(const std::shared_ptr<AsioTlsStream>& stream, const Dictionary::Ptr& message, boost::asio::yield_context yc);
|
static size_t SendMessage(const std::shared_ptr<AsioTlsStream>& stream, const Dictionary::Ptr& message, boost::asio::yield_context yc);
|
||||||
static size_t SendRawMessage(const std::shared_ptr<AsioTlsStream>& stream, const String& json, boost::asio::yield_context yc);
|
static size_t SendRawMessage(const std::shared_ptr<AsioTlsStream>& stream, const String& json, boost::asio::yield_context yc);
|
||||||
static StreamReadStatus ReadMessage(const Stream::Ptr& stream, String *message, StreamReadContext& src, bool may_wait = false, ssize_t maxMessageLength = -1);
|
|
||||||
static String ReadMessage(const std::shared_ptr<AsioTlsStream>& stream, ssize_t maxMessageLength = -1);
|
static String ReadMessage(const std::shared_ptr<AsioTlsStream>& stream, ssize_t maxMessageLength = -1);
|
||||||
static String ReadMessage(const std::shared_ptr<AsioTlsStream>& stream, boost::asio::yield_context yc, ssize_t maxMessageLength = -1);
|
static String ReadMessage(const std::shared_ptr<AsioTlsStream>& stream, boost::asio::yield_context yc, ssize_t maxMessageLength = -1);
|
||||||
|
|
||||||
static Dictionary::Ptr DecodeMessage(const String& message);
|
static Dictionary::Ptr DecodeMessage(const String& message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user