package com.intrbiz.bergamot.worker.engine.http; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpVersion; import org.apache.log4j.Logger; import com.codahale.metrics.Counter; import com.codahale.metrics.Timer; import com.intrbiz.Util; import com.intrbiz.bergamot.check.http.HTTPCheckBuilder; import com.intrbiz.bergamot.check.http.HTTPCheckResponse; import com.intrbiz.bergamot.model.message.ParameterMO; import com.intrbiz.bergamot.model.message.check.ExecuteCheck; import com.intrbiz.bergamot.model.message.reading.ReadingParcelMO; import com.intrbiz.bergamot.model.message.result.ActiveResultMO; import com.intrbiz.bergamot.model.message.result.ResultMO; import com.intrbiz.bergamot.queue.key.ReadingKey; import com.intrbiz.bergamot.worker.engine.AbstractExecutor; import com.intrbiz.gerald.source.IntelligenceSource; import com.intrbiz.gerald.witchcraft.Witchcraft; /** * Execute HTTP checks */ public class HTTPExecutor extends AbstractExecutor<HTTPEngine> { public static final String NAME = "http"; private Logger logger = Logger.getLogger(HTTPExecutor.class); private final Timer requestTimer; private final Counter failedRequests; public HTTPExecutor() { super(); // setup metrics IntelligenceSource source = Witchcraft.get().source("com.intrbiz.bergamot.http"); this.requestTimer = source.getRegistry().timer("all-http-requests"); this.failedRequests = source.getRegistry().counter("failed-http-requests"); } /** * Only execute Checks where the engine == "http" and (executor == "http" or executor == null) */ @Override public boolean accept(ExecuteCheck task) { return HTTPEngine.NAME.equalsIgnoreCase(task.getEngine()) && (HTTPExecutor.NAME.equalsIgnoreCase(task.getExecutor()) || Util.isEmpty(task.getExecutor())); } @Override public void execute(final ExecuteCheck executeCheck) { logger.info("Executing check : " + executeCheck.getEngine() + "::" + executeCheck.getExecutor() + "::" + executeCheck.getName() + " for " + executeCheck.getCheckType() + " " + executeCheck.getCheckId()); // time it final Timer.Context tctx = this.requestTimer.time(); try { if (Util.isEmpty(executeCheck.getParameter("host"))) throw new RuntimeException("The host must be defined!"); // HTTPCheckBuilder check = this.getEngine().getChecker().check(); // required parameters check.connect(executeCheck.getParameter("host")); // optional parameters // virtual host if (executeCheck.containsParameter("virtual_host")) check.host(executeCheck.getParameter("virtual_host", null)); // port number if (executeCheck.containsParameter("port")) check.port(executeCheck.getIntParameter("port", -1)); // ssl if (executeCheck.containsParameter("ssl")) check.ssl(executeCheck.getBooleanParameter("ssl", false)); if (executeCheck.containsParameter("permit_invalid_certs")) check.permitInvalidCerts(executeCheck.getBooleanParameter("permit_invalid_certs", false)); // the request if (executeCheck.containsParameter("version")) check.version(HttpVersion.valueOf(executeCheck.getParameter("version", "HTTP/1.1").toUpperCase())); if (executeCheck.containsParameter("method")) check.method(HttpMethod.valueOf(executeCheck.getParameter("method", "GET").toUpperCase())); if (executeCheck.containsParameter("path")) check.path(executeCheck.getParameter("path", "/")); // any headers for (ParameterMO header : executeCheck.getParametersStartingWith("header:")) { check.header(header.getName().substring("header:".length() + 1), header.getValue()); } // execute check.execute((response) -> { logger.info("Got response for HTTP check (" + executeCheck.getCheckId() + "/" + executeCheck.getId() + ")\n" + response); // compute the result ActiveResultMO resultMO = new ActiveResultMO().fromCheck(executeCheck); // check the response boolean cont = checkStatus(executeCheck, response, resultMO); if (cont) cont = checkRuntime(executeCheck, response, resultMO); if (cont) cont = checkLength(executeCheck, response, resultMO); if (cont) cont = checkContent(executeCheck, response, resultMO); // submit the result resultMO.setRuntime(response.getRuntime()); tctx.stop(); this.publishActiveResult(executeCheck, resultMO); // publish some metrics ReadingParcelMO readings = new ReadingParcelMO().fromCheck(executeCheck.getCheckId()); readings.longGaugeReading("response-time", "ms", response.getRuntime(), (long) executeCheck.getIntParameter("warning_response_time", 0), (long) executeCheck.getIntParameter("critical_response_time", 0), null, null); readings.longGaugeReading("content-length", "B", (long) response.getResponse().content().capacity()); readings.integerGaugeReading("status", null, response.getResponse().getStatus().code()); this.publishReading(new ReadingKey(executeCheck.getCheckId(), executeCheck.getProcessingPool()), readings); }, (error) -> { tctx.stop(); failedRequests.inc(); logger.error("Error for HTTP check (" + executeCheck.getCheckId() + "/" + executeCheck.getId() + ")", error); this.publishActiveResult(executeCheck, new ActiveResultMO().fromCheck(executeCheck).error(error)); }); } catch (Exception e) { logger.error("Failed to execute HTTP check", e); tctx.stop(); this.failedRequests.inc(); this.publishActiveResult(executeCheck, new ActiveResultMO().fromCheck(executeCheck).error(e)); } } protected boolean checkStatus(ExecuteCheck check, HTTPCheckResponse response, ResultMO resultMO) { if (check.getIntParameterCSV("status", "200, 301, 302, 304").stream().anyMatch((stat) -> { return response.getResponse().getStatus().code() == stat; })) { resultMO.ok("Got " + response.getResponse().getStatus() + ", " + response.getResponse().content().capacity() + " bytes, in " + response.getRuntime() + "ms"); return true; } resultMO.critical("Got " + response.getResponse().getStatus() + ", " + response.getResponse().content().capacity() + " bytes, in " + response.getRuntime() + "ms"); return false; } protected boolean checkRuntime(ExecuteCheck check, HTTPCheckResponse response, ResultMO resultMO) { if (check.containsParameter("critical_response_time")) { int criticalResponseTime = check.getIntParameter("critical_response_time", -1); if (criticalResponseTime > 0) { if (response.getRuntime() > criticalResponseTime) { resultMO.critical("Got " + response.getResponse().getStatus() + ", " + response.getResponse().content().capacity() + " bytes, in " + response.getRuntime() + "ms (slower than " + criticalResponseTime + "ms)"); return false; } } } else if (check.containsParameter("warning_response_time")) { int warningResponseTime = check.getIntParameter("warning_response_time", -1); if (warningResponseTime > 0) { if (response.getRuntime() > warningResponseTime) { resultMO.warning("Got " + response.getResponse().getStatus() + ", " + response.getResponse().content().capacity() + " bytes, in " + response.getRuntime() + "ms (slower than " + warningResponseTime + "ms)"); return false; } } } return true; } protected boolean checkLength(ExecuteCheck check, HTTPCheckResponse response, ResultMO resultMO) { if (check.containsParameter("length")) { int length = check.getIntParameter("length", -1); if (length > 0) { int contentLength = response.getResponse().content().capacity(); if (contentLength < length) { resultMO.critical("Got " + response.getResponse().getStatus() + ", " + response.getResponse().content().capacity() + " bytes, in " + response.getRuntime() + "ms (content length " + contentLength + " < " + length + "')"); return false; } } } return true; } protected boolean checkContent(ExecuteCheck check, HTTPCheckResponse response, ResultMO resultMO) { if (check.containsParameter("contains")) { String contains = check.getParameter("contains"); // get the content as a string // TODO: we should detect the charset rather than assuming UTF8 String content = response.getResponse().content().toString(Util.UTF8); // check we contain the given string if (! content.contains(contains)) { resultMO.critical("Got " + response.getResponse().getStatus() + ", " + response.getResponse().content().capacity() + " bytes, in " + response.getRuntime() + "ms (content does not contain: '" + contains + "')"); return false; } } return true; } }