#include "cli/consolecommand.hpp"
#include "config/configcompiler.hpp"
-#include "remote/apiclient.hpp"
#include "remote/consolehandler.hpp"
#include "remote/url.hpp"
#include "base/configwriter.hpp"
}
return suggestions;
-}
\ No newline at end of file
+}
#include "perfdata/elasticsearchwriter.hpp"
#include "perfdata/elasticsearchwriter-ti.cpp"
#include "remote/url.hpp"
-#include "remote/httprequest.hpp"
-#include "remote/httpresponse.hpp"
#include "icinga/compatutility.hpp"
#include "icinga/service.hpp"
#include "icinga/checkcommand.hpp"
#include "perfdata/influxdbwriter.hpp"
#include "perfdata/influxdbwriter-ti.cpp"
#include "remote/url.hpp"
-#include "remote/httprequest.hpp"
-#include "remote/httpresponse.hpp"
#include "icinga/service.hpp"
#include "icinga/macroprocessor.hpp"
#include "icinga/icingaapplication.hpp"
i2-remote.hpp
actionshandler.cpp actionshandler.hpp
apiaction.cpp apiaction.hpp
- apiclient.cpp apiclient.hpp
apifunction.cpp apifunction.hpp
apilistener.cpp apilistener.hpp apilistener-ti.hpp apilistener-configsync.cpp apilistener-filesync.cpp
apilistener-authority.cpp
eventshandler.cpp eventshandler.hpp
filterutility.cpp filterutility.hpp
httpchunkedencoding.cpp httpchunkedencoding.hpp
- httpclientconnection.cpp httpclientconnection.hpp
httphandler.cpp httphandler.hpp
- httprequest.cpp httprequest.hpp
- httpresponse.cpp httpresponse.hpp
httpserverconnection.cpp httpserverconnection.hpp
httputility.cpp httputility.hpp
infohandler.cpp infohandler.hpp
+++ /dev/null
-/* 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);
- }
-}
+++ /dev/null
-/* 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 */
+++ /dev/null
-/* 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();
-}
-
+++ /dev/null
-/* 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 */
#include "remote/i2-remote.hpp"
#include "remote/url.hpp"
-#include "remote/httpresponse.hpp"
#include "remote/httpserverconnection.hpp"
#include "remote/apiuser.hpp"
#include "base/registry.hpp"
+++ /dev/null
-/* 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;
-}
-
+++ /dev/null
-/* 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 */
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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 */
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 varr = params->Get(key);
return arr->Get(arr->GetLength() - 1);
}
-void HttpUtility::SendJsonError(HttpResponse& response, const Dictionary::Ptr& params,
- int code, const String& info, const String& diagnosticInformation)
+void HttpUtility::SendJsonBody(boost::beast::http::response<boost::beast::http::string_body>& response, const Dictionary::Ptr& params, const Value& val)
{
- Dictionary::Ptr result = new Dictionary();
- response.SetStatus(code, HttpUtility::GetErrorNameByCode(code));
- result->Set("error", code);
-
- bool verbose = false;
-
- if (params)
- verbose = HttpUtility::GetLastParameter(params, "verbose");
-
- if (!info.IsEmpty())
- result->Set("status", info);
-
- if (verbose) {
- if (!diagnosticInformation.IsEmpty())
- result->Set("diagnostic_information", diagnosticInformation);
- }
+ namespace http = boost::beast::http;
- HttpUtility::SendJsonBody(response, params, result);
+ 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());
}
void HttpUtility::SendJsonError(boost::beast::http::response<boost::beast::http::string_body>& response,
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";
- }
-}
-
#ifndef HTTPUTILITY_H
#define HTTPUTILITY_H
-#include "remote/httprequest.hpp"
-#include "remote/httpresponse.hpp"
#include "remote/url.hpp"
#include "base/dictionary.hpp"
-#include <string>
#include <boost/beast/http.hpp>
+#include <string>
namespace icinga
{
public:
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 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,
const String& verbose = String(), const String& diagnosticInformation = String());
-
-private:
- static String GetErrorNameByCode(int code);
-
};
}