Quality: Purge old HTTP code in lib/remote

This commit is contained in:
Michael Friedrich 2019-05-23 15:25:08 +02:00
parent 5d0af5c879
commit f933aafd29
15 changed files with 9 additions and 1162 deletions

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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
@ -27,10 +26,7 @@ set(remote_SOURCES
eventshandler.cpp eventshandler.hpp eventshandler.cpp eventshandler.hpp
filterutility.cpp filterutility.hpp filterutility.cpp filterutility.hpp
httpchunkedencoding.cpp httpchunkedencoding.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

View File

@ -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);
}
}

View File

@ -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 */

View File

@ -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();
}

View File

@ -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 */

View File

@ -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"

View File

@ -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;
}

View File

@ -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 */

View File

@ -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;
}

View File

@ -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 */

View File

@ -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";
}
}

View File

@ -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);
}; };
} }