package org.graylog2.plugin.httpmonitor; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.MetricSet; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Maps; import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; import com.ning.http.client.*; import org.apache.commons.lang3.StringUtils; import org.graylog2.plugin.ServerStatus; import org.graylog2.plugin.configuration.Configuration; import org.graylog2.plugin.configuration.ConfigurationRequest; import org.graylog2.plugin.configuration.fields.*; import org.graylog2.plugin.inputs.MessageInput; import org.graylog2.plugin.inputs.MisfireException; import org.graylog2.plugin.inputs.annotations.ConfigClass; import org.graylog2.plugin.inputs.annotations.FactoryClass; import org.graylog2.plugin.inputs.codecs.CodecAggregator; import org.graylog2.plugin.inputs.transports.Transport; import org.graylog2.plugin.journal.RawMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.ConnectException; import java.net.URI; import java.net.URL; import java.security.GeneralSecurityException; import java.security.KeyManagementException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; import java.util.concurrent.*; /** * Created on 17/6/15. */ public class HttpMonitorTransport implements Transport { private static final Logger LOGGER = LoggerFactory.getLogger(HttpMonitorTransport.class.getName()); private static final String CK_CONFIG_URL = "configURL"; private static final String CK_CONFIG_LABEL = "configLabel"; private static final String CK_CONFIG_METHOD = "configMethod"; private static final String CK_CONFIG_REQUEST_BODY = "configRequestBody"; private static final String CK_CONFIG_HEADERS_TO_SEND = "configHeadersToSend"; private static final String CK_CONFIG_USER_NAME = "configUsername"; private static final String CK_CONFIG_PASSWORD = "configPassword"; private static final String CK_CONFIG_TIMEOUT = "configTimeout"; private static final String CK_CONFIG_TIMEOUT_UNIT = "configTimeoutUnit"; private static final String CK_CONFIG_INTERVAL = "configInterval"; private static final String CK_CONFIG_INTERVAL_UNIT = "configIntervalUnit"; private static final String CK_CONFIG_HEADERS_TO_RECORD = "configHeadersToRecord"; private static final String CK_CONFIG_LOG_RESPONSE_BODY = "configLogResponseBody"; private static final String CK_CONFIG_HTTP_PROXY = "configHttpProxy"; private static final String METHOD_POST = "POST"; private static final String METHOD_HEAD = "HEAD"; private static final String METHOD_PUT = "PUT"; private static final String METHOD_GET = "GET"; private final Configuration configuration; private final MetricRegistry metricRegistry; private ServerStatus serverStatus; private ScheduledExecutorService executorService; private ScheduledFuture future; private MessageInput messageInput; @AssistedInject public HttpMonitorTransport(@Assisted Configuration configuration, MetricRegistry metricRegistry, ServerStatus serverStatus) { this.configuration = configuration; this.metricRegistry = metricRegistry; this.serverStatus = serverStatus; } @Override public void setMessageAggregator(CodecAggregator codecAggregator) { } @Override public void launch(MessageInput messageInput) throws MisfireException { this.messageInput = messageInput; URLMonitorConfig urlMonitorConfig = new URLMonitorConfig(); urlMonitorConfig.setUrl(configuration.getString(CK_CONFIG_URL)); urlMonitorConfig.setLabel(configuration.getString(CK_CONFIG_LABEL)); urlMonitorConfig.setMethod(configuration.getString(CK_CONFIG_METHOD)); String proxyUri = configuration.getString(CK_CONFIG_HTTP_PROXY); if (proxyUri != null && !proxyUri.isEmpty()) { urlMonitorConfig.setHttpProxyUri(URI.create(proxyUri)); } String requestHeaders = configuration.getString(CK_CONFIG_HEADERS_TO_SEND); if (StringUtils.isNotEmpty(requestHeaders)) { urlMonitorConfig.setRequestHeadersToSend( requestHeaders.split(",")); } urlMonitorConfig.setUsername(configuration.getString(CK_CONFIG_USER_NAME)); urlMonitorConfig.setPassword(configuration.getString(CK_CONFIG_PASSWORD)); urlMonitorConfig.setExecutionInterval(configuration.getInt(CK_CONFIG_INTERVAL)); urlMonitorConfig.setTimeout(configuration.getInt(CK_CONFIG_TIMEOUT)); urlMonitorConfig.setTimeoutUnit(TimeUnit.valueOf(configuration.getString(CK_CONFIG_TIMEOUT_UNIT))); urlMonitorConfig.setIntervalUnit(TimeUnit.valueOf(configuration.getString(CK_CONFIG_INTERVAL_UNIT))); // long timoutInMs = TimeUnit.MILLISECONDS.convert(urlMonitorConfig.getTimeout(), urlMonitorConfig.getTimeoutUnit()); // long intervalInMs = TimeUnit.MILLISECONDS.convert(urlMonitorConfig.getExecutionInterval(), urlMonitorConfig.getIntervalUnit()); // // if (intervalInMs <= timoutInMs) { // String message = MessageFormat.format("Timeout {0} {1} should be smaller than interval {2} {3}", // urlMonitorConfig.getTimeout(),urlMonitorConfig.getTimeoutUnit(), // urlMonitorConfig.getExecutionInterval(), urlMonitorConfig.getIntervalUnit()); // throw new MisfireException(message); // } urlMonitorConfig.setRequestBody(configuration.getString(CK_CONFIG_REQUEST_BODY)); urlMonitorConfig.setLogResponseBody(configuration.getBoolean(CK_CONFIG_LOG_RESPONSE_BODY)); String responseHeaders = configuration.getString(CK_CONFIG_HEADERS_TO_RECORD); if (StringUtils.isNotEmpty(responseHeaders)) { urlMonitorConfig.setResponseHeadersToRecord( responseHeaders.split(",")); } startMonitoring(urlMonitorConfig); } @Override public void stop() { if (future != null) { future.cancel(true); } if (executorService != null) { executorService.shutdownNow(); } } private void startMonitoring(URLMonitorConfig config) { executorService = Executors.newSingleThreadScheduledExecutor(); long initalDelayMs = TimeUnit.MILLISECONDS.convert(Math.round(Math.random() * 60), TimeUnit.SECONDS); long executionIntervalMs = TimeUnit.MILLISECONDS.convert(config.getExecutionInterval(), config.getIntervalUnit()); future = executorService.scheduleAtFixedRate(new MonitorTask(config, messageInput), initalDelayMs, executionIntervalMs, TimeUnit.MILLISECONDS); } @Override public MetricSet getMetricSet() { return null; } private static class MonitorTask implements Runnable { private URLMonitorConfig config; private MessageInput messageInput; private ObjectMapper mapper; private AsyncHttpClient httpClient; private AsyncHttpClient.BoundRequestBuilder requestBuilder; public MonitorTask(URLMonitorConfig config, MessageInput messageInput) { this.config = config; this.messageInput = messageInput; this.mapper = new ObjectMapper(); AsyncHttpClientConfig.Builder configBuilder = new AsyncHttpClientConfig.Builder(); configBuilder.setEnabledProtocols(new String[] {"TLSv1.2", "TLSv1.1", "TLSv1"}); configBuilder.setSSLContext(getSSLContext()); httpClient = new AsyncHttpClient(configBuilder.build()); buildRequest(); } //Accept all certficates private SSLContext getSSLContext() { try { SSLContext context = SSLContext.getInstance("SSL"); context.init(null, new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return null; } } }, null); return context; } catch (GeneralSecurityException e) { LOGGER.debug("Exception while creating certs ",e); } return null; } @Override public void run() { //send to http server try { long startTime = System.currentTimeMillis(); long time; Map<String, Object> eventdata = Maps.newHashMap(); eventdata.put("version", "1.1"); eventdata.put("_http_monitor_url", config.getUrl()); eventdata.put("_label", config.getLabel()); try { Response response = requestBuilder.execute().get(); long endTime = System.currentTimeMillis(); time = endTime - startTime; eventdata.put("host", response.getUri().getHost()); eventdata.put("_http_monitor_status", response.getStatusCode()); eventdata.put("_http_monitor_statusLine", response.getStatusText()); String responseBodyStr = new String(response.getResponseBodyAsBytes()); eventdata.put("_http_monitor_responseSize", responseBodyStr.length()); if (config.isLogResponseBody()) { eventdata.put("full_message", responseBodyStr); } String shortMessage = responseBodyStr.length() > 50 ? responseBodyStr.substring(0, 50) : responseBodyStr; if (shortMessage.isEmpty()) { shortMessage = "no_response"; } eventdata.put("short_message", shortMessage); if (config.getResponseHeadersToRecord() != null) { for (String header : config.getResponseHeadersToRecord()) { eventdata.put("_" + header, response.getHeader(header)); } } } catch (ExecutionException e) { eventdata.put("host", new URL(config.getUrl()).getHost()); eventdata.put("short_message", "Request failed :" + e.getMessage()); eventdata.put("_http_monitor_responseSize", 0); long endTime = System.currentTimeMillis(); time = endTime - startTime; //In case of connection timeout we get an execution exception with root cause as timeoutexception if (e.getCause() instanceof TimeoutException) { LOGGER.debug("Timeout while executing request for URL " + config.getUrl(), e); eventdata.put("_http_monitor_status", 998); } else if (e.getCause() instanceof ConnectException) { //In case of connect exception we get an execution exception with root cause as connectexception LOGGER.debug("Exception while executing request for URL " + config.getUrl(), e); eventdata.put("_http_monitor_status", 999); } else { //Any other exception.. LOGGER.debug("Exception while executing request for URL " + config.getUrl(), e); eventdata.put("_http_monitor_status", 997); } } eventdata.put("_http_monitor_responseTime", time); //publish to graylog server ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); mapper.writeValue(byteStream, eventdata); messageInput.processRawMessage(new RawMessage(byteStream.toByteArray())); byteStream.close(); } catch (InterruptedException | IOException e) { LOGGER.error("Exception while executing request for URL " + config.getUrl(), e); } } private void buildRequest() { //Build request object if (METHOD_POST.equals(config.getMethod())) { requestBuilder = httpClient.preparePost(config.getUrl()); } else if (METHOD_PUT.equals(config.getMethod())) { requestBuilder = httpClient.preparePut(config.getUrl()); } else if (METHOD_HEAD.equals(config.getMethod())) { requestBuilder = httpClient.prepareHead(config.getUrl()); } else { requestBuilder = httpClient.prepareGet(config.getUrl()); } if (StringUtils.isNotEmpty(config.getRequestBody())) { requestBuilder.setBody(config.getRequestBody()); } if (config.getRequestHeadersToSend() != null) { for (String header : config.getRequestHeadersToSend()) { String tokens[] = header.split(":"); requestBuilder.setHeader(tokens[0], tokens[1]); } } if (StringUtils.isNotEmpty(config.getUsername()) && StringUtils.isNotEmpty(config.getPassword())) { Realm realm = new Realm.RealmBuilder() .setPrincipal(config.getUsername()) .setPassword(config.getPassword()) .setScheme(Realm.AuthScheme.BASIC).build(); requestBuilder.setRealm(realm); } int timeoutInMs = (int) TimeUnit.MILLISECONDS.convert(config.getTimeout(), config.getTimeoutUnit()); requestBuilder.setRequestTimeout(timeoutInMs); if (config.getHttpProxyUri() != null) { ProxyServer proxyServer = new ProxyServer(config.getHttpProxyUri().getHost(), config.getHttpProxyUri().getPort()); requestBuilder.setProxyServer(proxyServer); } } } @FactoryClass public interface Factory extends Transport.Factory<HttpMonitorTransport> { @Override HttpMonitorTransport create(Configuration configuration); @Override Config getConfig(); } @ConfigClass public static class Config implements Transport.Config { @Override public ConfigurationRequest getRequestedConfiguration() { final ConfigurationRequest cr = new ConfigurationRequest(); cr.addField(new TextField(CK_CONFIG_URL, "URL to monitor", "", "")); cr.addField(new TextField(CK_CONFIG_LABEL, "Label", "", "Label to identify this HTTP monitor")); Map<String, String> httpMethods = new HashMap<>(); httpMethods.put(METHOD_GET, METHOD_GET); httpMethods.put(METHOD_POST, METHOD_POST); httpMethods.put(METHOD_PUT, METHOD_PUT); cr.addField(new DropdownField(CK_CONFIG_METHOD, "HTTP Method", "GET", httpMethods, "Label to identify this HTTP monitor", ConfigurationField.Optional.NOT_OPTIONAL)); cr.addField(new TextField(CK_CONFIG_REQUEST_BODY, "Request Body", "", "Request Body to send", ConfigurationField.Optional.OPTIONAL, TextField.Attribute.TEXTAREA)); cr.addField(new TextField(CK_CONFIG_HEADERS_TO_SEND, "Additional HTTP headers", "", "Add a comma separated list of additional HTTP headers to send. For example: Accept: application/json, X-Requester: Graylog2", ConfigurationField.Optional.OPTIONAL)); cr.addField(new TextField(CK_CONFIG_USER_NAME, "HTTP Basic Auth Username", "", "Username for HTTP Basic Authentication", ConfigurationField.Optional.OPTIONAL)); cr.addField(new TextField(CK_CONFIG_PASSWORD, "HTTP Basic Auth Password", "", "Password for HTTP Basic Authentication", ConfigurationField.Optional.OPTIONAL, TextField.Attribute.IS_PASSWORD)); cr.addField(new NumberField(CK_CONFIG_INTERVAL, "Interval", 1, "Time between between requests", ConfigurationField.Optional.NOT_OPTIONAL)); Map<String, String> timeUnits = DropdownField.ValueTemplates.timeUnits(); //Do not add nano seconds and micro seconds timeUnits.remove(TimeUnit.NANOSECONDS.toString()); timeUnits.remove(TimeUnit.MICROSECONDS.toString()); cr.addField(new DropdownField( CK_CONFIG_INTERVAL_UNIT, "Interval time unit", TimeUnit.MINUTES.toString(), timeUnits, ConfigurationField.Optional.NOT_OPTIONAL )); cr.addField(new NumberField(CK_CONFIG_TIMEOUT, "Timeout", 20, "Timeout for requests", ConfigurationField.Optional.NOT_OPTIONAL)); cr.addField(new DropdownField( CK_CONFIG_TIMEOUT_UNIT, "Timeout time unit", TimeUnit.SECONDS.toString(), timeUnits, ConfigurationField.Optional.NOT_OPTIONAL )); cr.addField(new TextField(CK_CONFIG_HTTP_PROXY, "HTTP Proxy URI", "", "URI of HTTP Proxy to be used if required e.g. http://myproxy:8888", ConfigurationField.Optional.OPTIONAL)); cr.addField(new TextField(CK_CONFIG_HEADERS_TO_RECORD, "Response headers to log", "", "Comma separated response headers to log. For example: Accept,Server,Expires", ConfigurationField.Optional.OPTIONAL)); cr.addField(new BooleanField(CK_CONFIG_LOG_RESPONSE_BODY, "Log full response body", false, "Select if the complete response body needs to be logged as part of message")); return cr; } } public static void main(String args[]) { URLMonitorConfig config = new URLMonitorConfig(); config.setUrl("https://www.skipper18.com"); config.setMethod("GET"); MonitorTask monitorTask = new MonitorTask(config,null); monitorTask.run(); } }