virtual bool OnInit(void) override { Application::InitializeBase(); Url::Ptr pUrl; if (argc < 2) { wxConfig config("IcingaStudio"); wxString wUrl; if (!config.Read("url", &wUrl)) wUrl = "https://localhost:5665/"; std::string url = wUrl.ToStdString(); ConnectForm f(NULL, new Url(url)); if (f.ShowModal() != wxID_OK) return false; pUrl = f.GetUrl(); url = pUrl->Format(true); wUrl = url; config.Write("url", wUrl); } else { pUrl = new Url(argv[1].ToStdString()); } MainForm *m = new MainForm(NULL, pUrl); m->Show(); return true; }
MainForm::MainForm(wxWindow *parent, const Url::Ptr& url) : MainFormBase(parent) { #ifdef _WIN32 SetIcon(wxICON(icinga)); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); #endif /* _WIN32 */ String port = url->GetPort(); if (port.IsEmpty()) port = "5665"; m_ApiClient = new ApiClient(url->GetHost(), port, url->GetUsername(), url->GetPassword()); m_ApiClient->GetTypes(boost::bind(&MainForm::TypesCompletionHandler, this, _1, _2, true)); std::string title = url->Format() + " - Icinga Studio"; SetTitle(title); m_ObjectsList->InsertColumn(0, "Name", 0, 300); m_PropertyGrid->SetColumnCount(3); }
/** * Sends the request via REST API and returns the parsed response. * * @param tlsStream Caller must prepare TLS stream/handshake. * @param url Fully prepared Url object. * @return A dictionary decoded from JSON. */ Dictionary::Ptr ConsoleCommand::SendRequest() { namespace beast = boost::beast; namespace http = beast::http; l_TlsStream = ConsoleCommand::Connect(); Defer s ([&]() { l_TlsStream->next_layer().shutdown(); }); http::request<http::string_body> request(http::verb::post, std::string(l_Url->Format(false)), 10); request.set(http::field::user_agent, "Icinga/DebugConsole/" + Application::GetAppVersion()); request.set(http::field::host, l_Url->GetHost() + ":" + l_Url->GetPort()); request.set(http::field::accept, "application/json"); request.set(http::field::authorization, "Basic " + Base64::Encode(l_Url->GetUsername() + ":" + l_Url->GetPassword())); try { http::write(*l_TlsStream, request); l_TlsStream->flush(); } catch (const std::exception &ex) { Log(LogWarning, "DebugConsole") << "Cannot write HTTP request to REST API at URL '" << l_Url->Format(true) << "': " << ex.what(); throw; } http::parser<false, http::string_body> parser; beast::flat_buffer buf; try { http::read(*l_TlsStream, buf, parser); } catch (const std::exception &ex) { Log(LogWarning, "DebugConsole") << "Failed to parse HTTP response from REST API at URL '" << l_Url->Format(true) << "': " << ex.what(); throw; } auto &response(parser.get()); /* Handle HTTP errors first. */ if (response.result() != http::status::ok) { String message = "HTTP request failed; Code: " + Convert::ToString(response.result()) + "; Body: " + response.body(); BOOST_THROW_EXCEPTION(ScriptError(message)); } Dictionary::Ptr jsonResponse; auto &body(response.body()); //Log(LogWarning, "Console") // << "Got response: " << response.body(); try { jsonResponse = JsonDecode(body); } catch (...) { String message = "Cannot parse JSON response body: " + response.body(); BOOST_THROW_EXCEPTION(ScriptError(message)); } return jsonResponse; }
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(); } }