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" }); std::map<String, std::vector<String> > params; params["session"].push_back(session); params["command"].push_back(command); params["sandboxed"].emplace_back(sandboxed ? "1" : "0"); url->SetQuery(params); 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); } }
/** * Executes the auto completion script via HTTP and returns HTTP and user errors. * * @param session Local session handler. * @param command The auto completion string. * @param sandboxed Whether to run this sandboxed. * @return Result value, also contains user errors. */ Array::Ptr ConsoleCommand::AutoCompleteScript(const String& session, const String& command, bool sandboxed) { /* Extend the url parameters for the request. */ l_Url->SetPath({ "v1", "console", "auto-complete-script" }); l_Url->SetQuery({ {"session", session}, {"command", command}, {"sandboxed", sandboxed ? "1" : "0"} }); Dictionary::Ptr jsonResponse = SendRequest(); /* Extract the result, and handle user input errors too. */ Array::Ptr results = jsonResponse->Get("results"); Array::Ptr suggestions; if (results && results->GetLength() > 0) { Dictionary::Ptr resultInfo = results->Get(0); if (resultInfo->Get("code") >= 200 && resultInfo->Get("code") <= 299) { suggestions = resultInfo->Get("suggestions"); } else { String errorMessage = resultInfo->Get("status"); BOOST_THROW_EXCEPTION(ScriptError(errorMessage)); } } return suggestions; }
void ApiClient::GetObjects(const String& pluralType, const ObjectsCompletionCallback& callback, const std::vector<String>& names, const std::vector<String>& attrs, const std::vector<String>& joins, bool all_joins) const { Url::Ptr url = new Url(); url->SetScheme("https"); url->SetHost(m_Connection->GetHost()); url->SetPort(m_Connection->GetPort()); std::vector<String> path; path.push_back("v1"); path.push_back("objects"); path.push_back(pluralType); url->SetPath(path); std::map<String, std::vector<String> > params; for (const String& name : names) { params[pluralType.ToLower()].push_back(name); } for (const String& attr : attrs) { params["attrs"].push_back(attr); } for (const String& join : joins) { params["joins"].push_back(join); } params["all_joins"].push_back(all_joins ? "1" : "0"); url->SetQuery(params); try { boost::shared_ptr<HttpRequest> req = m_Connection->NewRequest(); req->RequestMethod = "GET"; req->RequestUrl = url; req->AddHeader("Authorization", "Basic " + Base64::Encode(m_User + ":" + m_Password)); req->AddHeader("Accept", "application/json"); m_Connection->SubmitRequest(req, boost::bind(ObjectsHttpCompletionCallback, _1, _2, callback)); } catch (const std::exception& ex) { callback(boost::current_exception(), std::vector<ApiObject::Ptr>()); } }
/** * Executes the DSL script via HTTP and returns HTTP and user errors. * * @param session Local session handler. * @param command The DSL string. * @param sandboxed Whether to run this sandboxed. * @return Result value, also contains user errors. */ Value ConsoleCommand::ExecuteScript(const String& session, const String& command, bool sandboxed) { /* Extend the url parameters for the request. */ l_Url->SetPath({"v1", "console", "execute-script"}); l_Url->SetQuery({ {"session", session}, {"command", command}, {"sandboxed", sandboxed ? "1" : "0"} }); Dictionary::Ptr jsonResponse = SendRequest(); /* Extract the result, and handle user input errors too. */ Array::Ptr results = jsonResponse->Get("results"); Value result; if (results && results->GetLength() > 0) { Dictionary::Ptr resultInfo = results->Get(0); if (resultInfo->Get("code") >= 200 && resultInfo->Get("code") <= 299) { result = resultInfo->Get("result"); } else { String errorMessage = resultInfo->Get("status"); 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)); } } return result; }
void ApiClient::GetTypes(const TypesCompletionCallback& callback) const { Url::Ptr url = new Url(); url->SetScheme("https"); url->SetHost(m_Connection->GetHost()); url->SetPort(m_Connection->GetPort()); std::vector<String> path; path.push_back("v1"); path.push_back("types"); url->SetPath(path); try { boost::shared_ptr<HttpRequest> req = m_Connection->NewRequest(); req->RequestMethod = "GET"; req->RequestUrl = url; req->AddHeader("Authorization", "Basic " + Base64::Encode(m_User + ":" + m_Password)); req->AddHeader("Accept", "application/json"); m_Connection->SubmitRequest(req, boost::bind(TypesHttpCompletionCallback, _1, _2, callback)); } catch (const std::exception& ex) { callback(boost::current_exception(), std::vector<ApiType::Ptr>()); } }
void InfluxdbWriter::Flush(void) { Stream::Ptr stream = Connect(); // Unable to connect, play it safe and lose the data points // to avoid a memory leak if (!stream.get()) { m_DataBuffer->Clear(); return; } Url::Ptr url = new Url(); url->SetScheme(GetSslEnable() ? "https" : "http"); url->SetHost(GetHost()); url->SetPort(GetPort()); std::vector<String> path; path.push_back("write"); url->SetPath(path); url->AddQueryElement("db", GetDatabase()); url->AddQueryElement("precision", "s"); if (!GetUsername().IsEmpty()) url->AddQueryElement("u", GetUsername()); if (!GetPassword().IsEmpty()) url->AddQueryElement("p", GetPassword()); // Ensure you hold a lock against m_DataBuffer so that things // don't go missing after creating the body and clearing the buffer String body = Utility::Join(m_DataBuffer, '\n', false); m_DataBuffer->Clear(); HttpRequest req(stream); req.RequestMethod = "POST"; req.RequestUrl = url; try { req.WriteBody(body.CStr(), body.GetLength()); req.Finish(); } catch (const std::exception&) { Log(LogWarning, "InfluxdbWriter") << "Cannot write to TCP socket on host '" << GetHost() << "' port '" << GetPort() << "'."; return; } HttpResponse resp(stream, req); StreamReadContext context; try { resp.Parse(context, true); } catch (const std::exception&) { Log(LogWarning, "InfluxdbWriter") << "Cannot read from TCP socket from host '" << GetHost() << "' port '" << GetPort() << "'."; return; } if (resp.StatusCode != 204) { Log(LogWarning, "InfluxdbWriter") << "Unexpected response code " << resp.StatusCode; } }
void InfluxdbWriter::Flush() { String body = boost::algorithm::join(m_DataBuffer, "\n"); m_DataBuffer.clear(); Stream::Ptr stream; try { stream = Connect(); } catch (const std::exception& ex) { Log(LogWarning, "InfluxDbWriter") << "Flush failed, cannot connect to InfluxDB."; return; } if (!stream) return; Url::Ptr url = new Url(); url->SetScheme(GetSslEnable() ? "https" : "http"); url->SetHost(GetHost()); url->SetPort(GetPort()); std::vector<String> path; path.emplace_back("write"); url->SetPath(path); url->AddQueryElement("db", GetDatabase()); url->AddQueryElement("precision", "s"); if (!GetUsername().IsEmpty()) url->AddQueryElement("u", GetUsername()); if (!GetPassword().IsEmpty()) url->AddQueryElement("p", GetPassword()); HttpRequest req(stream); req.RequestMethod = "POST"; req.RequestUrl = url; try { req.WriteBody(body.CStr(), body.GetLength()); req.Finish(); } catch (const std::exception& ex) { Log(LogWarning, "InfluxdbWriter") << "Cannot write to TCP socket on host '" << GetHost() << "' port '" << GetPort() << "'."; throw ex; } HttpResponse resp(stream, req); StreamReadContext context; try { while (resp.Parse(context, true) && !resp.Complete) ; /* Do nothing */ } catch (const std::exception& ex) { Log(LogWarning, "InfluxdbWriter") << "Failed to parse HTTP response from host '" << GetHost() << "' port '" << GetPort() << "': " << DiagnosticInformation(ex); throw ex; } if (!resp.Complete) { Log(LogWarning, "InfluxdbWriter") << "Failed to read a complete HTTP response from the InfluxDB server."; return; } if (resp.StatusCode != 204) { Log(LogWarning, "InfluxdbWriter") << "Unexpected response code: " << resp.StatusCode; String contentType = resp.Headers->Get("content-type"); if (contentType != "application/json") { Log(LogWarning, "InfluxdbWriter") << "Unexpected Content-Type: " << contentType; return; } size_t responseSize = resp.GetBodySize(); boost::scoped_array<char> buffer(new char[responseSize + 1]); resp.ReadBody(buffer.get(), responseSize); buffer.get()[responseSize] = '\0'; Dictionary::Ptr jsonResponse; try { jsonResponse = JsonDecode(buffer.get()); } catch (...) { Log(LogWarning, "InfluxdbWriter") << "Unable to parse JSON response:\n" << buffer.get(); return; } String error = jsonResponse->Get("error"); Log(LogCritical, "InfluxdbWriter") << "InfluxDB error message:\n" << error; return; } }
void ElasticsearchWriter::SendRequest(const String& body) { namespace beast = boost::beast; namespace http = beast::http; Url::Ptr url = new Url(); url->SetScheme(GetEnableTls() ? "https" : "http"); url->SetHost(GetHost()); url->SetPort(GetPort()); std::vector<String> path; /* Specify the index path. Best practice is a daily rotation. * Example: http://localhost:9200/icinga2-2017.09.11?pretty=1 */ path.emplace_back(GetIndex() + "-" + Utility::FormatDateTime("%Y.%m.%d", Utility::GetTime())); /* ES 6 removes multiple _type mappings: https://www.elastic.co/guide/en/elasticsearch/reference/6.x/removal-of-types.html * Best practice is to statically define 'doc', as ES 5.X does not allow types starting with '_'. */ path.emplace_back("doc"); /* Use the bulk message format. */ path.emplace_back("_bulk"); url->SetPath(path); OptionalTlsStream stream; try { stream = Connect(); } catch (const std::exception& ex) { Log(LogWarning, "ElasticsearchWriter") << "Flush failed, cannot connect to Elasticsearch: " << DiagnosticInformation(ex, false); return; } Defer s ([&stream]() { if (stream.first) { stream.first->next_layer().shutdown(); } }); http::request<http::string_body> request (http::verb::post, std::string(url->Format(true)), 10); request.set(http::field::user_agent, "Icinga/" + Application::GetAppVersion()); request.set(http::field::host, url->GetHost() + ":" + url->GetPort()); /* Specify required headers by Elasticsearch. */ request.set(http::field::accept, "application/json"); /* Use application/x-ndjson for bulk streams. While ES * is able to handle application/json, the newline separator * causes problems with Logstash (#6609). */ request.set(http::field::content_type, "application/x-ndjson"); /* Send authentication if configured. */ String username = GetUsername(); String password = GetPassword(); if (!username.IsEmpty() && !password.IsEmpty()) request.set(http::field::authorization, "Basic " + Base64::Encode(username + ":" + password)); request.body() = body; request.set(http::field::content_length, request.body().size()); /* Don't log the request body to debug log, this is already done above. */ Log(LogDebug, "ElasticsearchWriter") << "Sending " << request.method_string() << " request" << ((!username.IsEmpty() && !password.IsEmpty()) ? " with basic auth" : "" ) << " to '" << url->Format() << "'."; try { if (stream.first) { http::write(*stream.first, request); stream.first->flush(); } else { http::write(*stream.second, request); stream.second->flush(); } } catch (const std::exception&) { Log(LogWarning, "ElasticsearchWriter") << "Cannot write to HTTP API on host '" << GetHost() << "' port '" << GetPort() << "'."; throw; } http::parser<false, http::string_body> parser; beast::flat_buffer buf; try { if (stream.first) { http::read(*stream.first, buf, parser); } else { http::read(*stream.second, buf, parser); } } catch (const std::exception& ex) { Log(LogWarning, "ElasticsearchWriter") << "Failed to parse HTTP response from host '" << GetHost() << "' port '" << GetPort() << "': " << DiagnosticInformation(ex, false); throw; } auto& response (parser.get()); if (response.result_int() > 299) { if (response.result() == http::status::unauthorized) { /* More verbose error logging with Elasticsearch is hidden behind a proxy. */ if (!username.IsEmpty() && !password.IsEmpty()) { Log(LogCritical, "ElasticsearchWriter") << "401 Unauthorized. Please ensure that the user '" << username << "' is able to authenticate against the HTTP API/Proxy."; } else { Log(LogCritical, "ElasticsearchWriter") << "401 Unauthorized. The HTTP API requires authentication but no username/password has been configured."; } return; } std::ostringstream msgbuf; msgbuf << "Unexpected response code " << response.result_int() << " from URL '" << url->Format() << "'"; auto& contentType (response[http::field::content_type]); if (contentType != "application/json" && contentType != "application/json; charset=utf-8") { msgbuf << "; Unexpected Content-Type: '" << contentType << "'"; } auto& body (response.body()); #ifdef I2_DEBUG msgbuf << "; Response body: '" << body << "'"; #endif /* I2_DEBUG */ Dictionary::Ptr jsonResponse; try { jsonResponse = JsonDecode(body); } catch (...) { Log(LogWarning, "ElasticsearchWriter") << "Unable to parse JSON response:\n" << body; return; } String error = jsonResponse->Get("error"); Log(LogCritical, "ElasticsearchWriter") << "Error: '" << error << "'. " << msgbuf.str(); } }