package org.infosec.ismp.poller.monitor.http;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NoRouteToHostException;
import java.net.Socket;
import java.net.SocketException;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;
import org.apache.commons.lang.StringUtils;
import org.infosec.ismp.model.poller.MonitoredService;
import org.infosec.ismp.model.poller.NetworkInterface;
import org.infosec.ismp.model.poller.NetworkInterfaceNotSupportedException;
import org.infosec.ismp.model.poller.PollStatus;
import org.infosec.ismp.model.poller.monitors.IPv4Monitor;
import org.infosec.ismp.util.Base64;
import org.infosec.ismp.util.IPLike;
import org.infosec.ismp.util.ParameterMap;
import org.infosec.ismp.util.ThreadCategory;
import org.infosec.ismp.util.TimeoutTracker;
/**
* This class is designed to be used by the service poller framework to test the availability
* of the HTTP service on remote interfaces. The class implements the ServiceMonitor interface
* that allows it to be used along with other plug-ins by the service poller framework.
*
*
*/
public class HttpMonitor extends IPv4Monitor {
/**
* Default HTTP ports.
*/
private static final int[] DEFAULT_PORTS = { 80, 8080, 8888 };
/**
* Default retries.
*/
private static final int DEFAULT_RETRY = 0;
/**
* Default timeout. Specifies how long (in milliseconds) to block waiting for data from the
* monitored interface.
*/
private static final int DEFAULT_TIMEOUT = 3000; // 3 second timeout on read()
/**
* Poll the specified address for HTTP service availability.
*
* During the poll an attempt is made to connect on the specified port(s) (by default TCP
* ports 80, 8080, 8888). If the connection request is successful, an HTTP 'GET' command is
* sent to the interface. The response is parsed and a return code extracted and verified.
* Provided that the interface's response is valid we set the service status to
* SERVICE_AVAILABLE and return.
* @param parameters
* The package parameters (timeout, retry, and others) to be used for this poll.
* @return The availability of the interface and if a transition event should be suppressed.
*
*/
public PollStatus poll(MonitoredService svc, Map<String, Object> parameters) {
NetworkInterface iface = svc.getNetInterface();
if (iface.getType() != NetworkInterface.TYPE_IPV4) {
throw new NetworkInterfaceNotSupportedException(
"Unsupported interface type, only TYPE_IPV4 currently supported");
}
// Cycle through the port list
//
int currentPort = -1;
HttpMonitorClient httpClient = new HttpMonitorClient(iface,
new TreeMap<String, Object>(parameters));
for (int portIndex = 0; portIndex < determinePorts(httpClient
.getParameters()).length
&& httpClient.getPollStatus() != PollStatus.SERVICE_AVAILABLE; portIndex++) {
currentPort = determinePorts(httpClient.getParameters())[portIndex];
httpClient.setTimeoutTracker(new TimeoutTracker(parameters,
DEFAULT_RETRY, DEFAULT_TIMEOUT));
log().debug(
"Port = " + currentPort + ", Address = "
+ (iface.getAddress()) + ", "
+ httpClient.getTimeoutTracker());
httpClient.setCurrentPort(currentPort);
for (httpClient.getTimeoutTracker().reset(); httpClient
.getTimeoutTracker().shouldRetry()
&& httpClient.getPollStatus() != PollStatus.SERVICE_AVAILABLE; httpClient
.getTimeoutTracker().nextAttempt()) {
try {
httpClient.getTimeoutTracker().startAttempt();
httpClient.connect();
log().debug(
"HttpMonitor: connected to host: "
+ (iface.getAddress()) + " on port: "
+ currentPort);
httpClient.sendHttpCommand();
if (httpClient.isEndOfStream()) {
continue;
}
httpClient.setResponseTime(httpClient.getTimeoutTracker()
.elapsedTimeInMillis());
logResponseTimes(httpClient.getResponseTime(),
httpClient.getCurrentLine());
if (httpClient.getPollStatus() == PollStatus.SERVICE_AVAILABLE
&& StringUtils.isNotBlank(httpClient
.getResponseText())) {
httpClient
.setPollStatus(PollStatus.SERVICE_UNAVAILABLE);
httpClient.readLinedMatching();
if (httpClient.isEndOfStream()) {
continue;
}
httpClient.read();
if (!httpClient.isResponseTextFound()) {
String message = "Matching text: ["
+ httpClient.getResponseText()
+ "] not found in body of HTTP response";
log().debug(message);
httpClient.setReason("Matching text: ["
+ httpClient.getResponseText()
+ "] not found in body of HTTP response");
}
}
} catch (NoRouteToHostException e) {
log().warn(
"checkStatus: No route to host exception for address "
+ (iface.getAddress()), e);
portIndex = determinePorts(httpClient.getParameters()).length; // Will cause outer for(;;) to terminate
httpClient.setReason("No route to host exception");
} catch (InterruptedIOException e) {
log().info(
"checkStatus: did not connect to host with "
+ httpClient.getTimeoutTracker().toString(),
e);
httpClient.setReason("HTTP connection timeout");
} catch (ConnectException e) {
log().warn(
"Connection exception for "
+ (iface.getAddress())
+ ":"
+ determinePorts(httpClient.getParameters())[portIndex],
e);
httpClient
.setReason("HTTP connection exception on port: "
+ determinePorts(httpClient.getParameters())[portIndex]
+ ": " + e.getMessage());
} catch (IOException e) {
e.fillInStackTrace();
log().warn(
"IOException while polling address "
+ (iface.getAddress()), e);
httpClient.setReason("IOException while polling address: "
+ (iface.getAddress()) + ": " + e.getMessage());
} finally {
httpClient.closeConnection();
}
} // end for (attempts)
} // end for (ports)
return httpClient.determinePollStatusResponse();
}
private void logResponseTimes(Double responseTime, String line) {
if (log().isDebugEnabled()) {
log().debug("poll: response= " + line);
log().debug("poll: responseTime= " + responseTime + "ms");
}
}
protected Socket wrapSocket(final Socket socket) throws IOException {
return socket;
}
String determineBasicAuthentication(final Map<String, Object> parameters) {
String credentials = ParameterMap.getKeyedString(parameters,
"basic-authentication", null);
if (isNotBlank(credentials)) {
credentials = new String(
Base64.encodeBase64(credentials.getBytes()));
} else {
String user = ParameterMap.getKeyedString(parameters, "user", null);
if (isBlank(user)) {
credentials = null;
} else {
String passwd = ParameterMap.getKeyedString(parameters,
"password", "");
credentials = new String(
Base64.encodeBase64((user + ":" + passwd).getBytes()));
}
}
return credentials;
}
protected int[] determinePorts(final Map<String, Object> parameters) {
return ParameterMap.getKeyedIntegerArray(parameters, "port",
DEFAULT_PORTS);
}
private boolean isNotBlank(String str) {
return org.apache.commons.lang.StringUtils.isNotBlank(str);
}
private boolean isBlank(String str) {
return org.apache.commons.lang.StringUtils.isBlank(str);
}
final class HttpMonitorClient {
/**
* Default URL to 'GET'
*/
private static final String DEFAULT_URL = "/";
/**
* Default HTTP ports.
*/
// private static final int[] DEFAULT_PORTS = { 80, 8080, 8888 };
private double m_responseTime;
NetworkInterface m_iface;
Map<String, Object> m_parameters;
String m_httpCmd;
Socket m_httpSocket;
private BufferedReader m_lineRdr;
private String m_currentLine;
private int m_serviceStatus;
private String m_reason;
private final StringBuffer m_html = new StringBuffer();
private int m_serverResponseCode;
private TimeoutTracker m_timeoutTracker;
private int m_currentPort;
private String m_responseText;
private boolean m_responseTextFound = false;
HttpMonitorClient(NetworkInterface iface, TreeMap<String, Object> parameters) {
m_iface = iface;
m_parameters = parameters;
buildCommand();
m_serviceStatus = PollStatus.SERVICE_UNAVAILABLE;
m_responseText = determineResponseText(parameters);
}
public void read() throws IOException {
for (int nullCount = 0; nullCount < 2;) {
readLinedMatching();
if (isEndOfStream()) {
nullCount++;
}
}
}
public int getCurrentPort() {
return m_currentPort;
}
public Map<String, Object> getParameters() {
return m_parameters;
}
public boolean isResponseTextFound() {
return m_responseTextFound;
}
public void setResponseTextFound(boolean found) {
m_responseTextFound = found;
}
public boolean checkCurrentLineMatchesResponseText() {
if (m_responseText.charAt(0) == '~' && !m_responseTextFound) {
m_responseTextFound = m_currentLine.matches(m_responseText
.substring(1));
} else {
m_responseTextFound = (m_currentLine.indexOf(m_responseText) != -1 ? true
: false);
}
return m_responseTextFound;
}
public String getResponseText() {
return m_responseText;
}
public void setResponseText(String responseText) {
m_responseText = responseText;
}
public void setCurrentPort(int currentPort) {
m_currentPort = currentPort;
}
public TimeoutTracker getTimeoutTracker() {
return m_timeoutTracker;
}
public void setTimeoutTracker(TimeoutTracker tracker) {
m_timeoutTracker = tracker;
}
public Double getResponseTime() {
return m_responseTime;
}
public void setResponseTime(double elapsedTimeInMillis) {
m_responseTime = elapsedTimeInMillis;
}
protected void connect() throws IOException, SocketException {
m_httpSocket = new Socket();
m_httpSocket
.connect(
new InetSocketAddress(((InetAddress) m_iface
.getAddress()), m_currentPort),
m_timeoutTracker.getConnectionTimeout());
m_serviceStatus = PollStatus.SERVICE_UNRESPONSIVE;
m_httpSocket.setSoTimeout(m_timeoutTracker.getSoTimeout());
m_httpSocket = wrapSocket(m_httpSocket);
}
public void closeConnection() {
try {
if (m_httpSocket != null) {
m_httpSocket.close();
m_httpSocket = null;
}
} catch (IOException e) {
e.fillInStackTrace();
log().warn("Error closing socket connection", e);
}
}
public int getPollStatus() {
return m_serviceStatus;
}
public void setPollStatus(int serviceStatus) {
m_serviceStatus = serviceStatus;
}
public String getCurrentLine() {
return m_currentLine;
}
public int getServerResponse() {
return m_serverResponseCode;
}
private void determineServerInitialResponse() {
int serverResponseValue = -1;
if (m_currentLine != null) {
if (m_currentLine.startsWith("HTTP/")) {
serverResponseValue = parseHttpResponse();
if (IPLike.matchNumericListOrRange(
String.valueOf(serverResponseValue),
determineResponse(m_parameters))) {
log().debug(
"determineServerResponse: valid server response: "
+ serverResponseValue + " found.");
m_serviceStatus = PollStatus.SERVICE_AVAILABLE;
} else {
m_serviceStatus = PollStatus.SERVICE_UNAVAILABLE;
StringBuffer sb = new StringBuffer();
sb.append("HTTP response value: ");
sb.append(serverResponseValue);
sb.append(". Expecting: ");
sb.append(determineResponse(m_parameters));
sb.append(".");
m_reason = sb.toString();
}
}
}
m_serverResponseCode = serverResponseValue;
}
private int parseHttpResponse() {
StringTokenizer t = new StringTokenizer(m_currentLine);
t.nextToken();
int serverResponse = -1;
try {
serverResponse = Integer.parseInt(t.nextToken());
} catch (NumberFormatException nfE) {
log().info(
"Error converting response code from host = "
+ (m_iface.getAddress()) + ", response = "
+ m_currentLine);
}
return serverResponse;
}
public boolean isEndOfStream() {
boolean eos = false;
if (m_currentLine == null) {
eos = true;
}
return eos;
}
public String readLinedMatching() throws IOException {
m_currentLine = m_lineRdr.readLine();
if (determineVerbosity(m_parameters)) {
log().debug("\t<<: " + m_currentLine);
}
m_html.append(m_currentLine);
if (m_responseText != null && m_currentLine != null
&& !m_responseTextFound) {
if (checkCurrentLineMatchesResponseText()) {
log().debug("response-text: " + m_responseText + ": found.");
m_serviceStatus = PollStatus.SERVICE_AVAILABLE;
}
}
return m_currentLine;
}
public void sendHttpCommand() throws IOException {
if (determineVerbosity(m_parameters)) {
log().debug("Sending HTTP command: " + m_httpCmd);
}
m_httpSocket.getOutputStream().write(m_httpCmd.getBytes());
m_lineRdr = new BufferedReader(new InputStreamReader(
m_httpSocket.getInputStream()));
readLinedMatching();
if (determineVerbosity(m_parameters)) {
log().debug("Server response: " + m_currentLine);
}
determineServerInitialResponse();
}
private void buildCommand() {
/*
* Sorting this map just in case the poller gets changed and the Map
* is no longer a TreeMap.
*/
StringBuilder sb = new StringBuilder();
sb.append("GET ").append(determineUrl(m_parameters))
.append(" HTTP/1.1\r\n");
sb.append("Connection: CLOSE \r\n");
sb.append("Host: ").append(determineVirtualHost(m_iface, m_parameters))
.append("\r\n");
sb.append("User-Agent: ").append(determineUserAgent(m_parameters))
.append("\r\n");
if (determineBasicAuthentication(m_parameters) != null) {
sb.append("Authorization: Basic ")
.append(determineBasicAuthentication(m_parameters))
.append("\r\n");
}
for (String string : m_parameters.keySet()) {
String parmKey = string;
if (parmKey.matches("header[0-9]+$")) {
sb.append(determineHttpHeader(m_parameters, parmKey)).append(
"\r\n");
}
}
sb.append("\r\n");
final String cmd = sb.toString();
log().debug("checkStatus: cmd:\n" + cmd);
m_httpCmd = cmd;
}
public void setReason(final String reason) {
m_reason = reason;
}
public String getReason() {
return m_reason;
}
public Socket getHttpSocket() {
return m_httpSocket;
}
public void setHttpSocket(Socket httpSocket) {
m_httpSocket = httpSocket;
}
protected PollStatus determinePollStatusResponse() {
/*
Add the 'qualifier' parm to the parameter map. This parm will
contain the port on which the service was found if AVAILABLE or
will contain a comma delimited list of the port(s) which were
tried if the service is UNAVAILABLE
*/
if (getPollStatus() == PollStatus.SERVICE_UNAVAILABLE) {
//
// Build port string
//
StringBuffer testedPorts = new StringBuffer();
for (int i = 0; i < determinePorts(getParameters()).length; i++) {
if (i == 0) {
testedPorts.append(determinePorts(getParameters())[0]);
} else {
testedPorts.append(',').append(
determinePorts(getParameters())[i]);
}
}
// Add to parameter map
getParameters().put("qualifier", testedPorts.toString());
String reason = getReason();
reason += "/Ports: " + testedPorts.toString();
setReason(reason);
log().debug("checkStatus: Reason: \"" + getReason() + "\"");
return PollStatus.unavailable(getReason());
} else if (getPollStatus() == PollStatus.SERVICE_AVAILABLE) {
getParameters()
.put("qualifier", Integer.toString(getCurrentPort()));
return PollStatus.available(getResponseTime());
} else {
return PollStatus.get(getPollStatus(), getReason());
}
}
private String determineResponseText(final Map<String, Object> parameters) {
return ParameterMap.getKeyedString(parameters, "response-text", null);
}
private String determineResponse(final Map<String, Object> parameters) {
return ParameterMap.getKeyedString(parameters, "response",
determineDefaultResponseRange(determineUrl(parameters)));
}
private String determineUrl(final Map<String, Object> parameters) {
return ParameterMap.getKeyedString(parameters, "url", DEFAULT_URL);
}
protected int[] determinePorts(final Map<String, Object> parameters) {
return ParameterMap.getKeyedIntegerArray(parameters, "port",
DEFAULT_PORTS);
}
private String determineDefaultResponseRange(String url) {
if (url == null || url.equals(DEFAULT_URL)) {
return "100-499";
}
return "100-399";
}
private boolean isNotBlank(String str) {
return org.apache.commons.lang.StringUtils.isNotBlank(str);
}
private boolean isBlank(String str) {
return org.apache.commons.lang.StringUtils.isBlank(str);
}
// protected Socket wrapSocket(final Socket socket) throws IOException {
// return socket;
// }
protected ThreadCategory log() {
return ThreadCategory.getInstance(getClass());
}
private boolean determineVerbosity(final Map<String, Object> parameters) {
final String verbose = ParameterMap.getKeyedString(parameters,
"verbose", null);
return (verbose != null && verbose.equalsIgnoreCase("true")) ? true
: false;
}
private String determineVirtualHost(NetworkInterface iface,
final Map<String, Object> parameters) {
boolean res = ParameterMap.getKeyedBoolean(parameters, "resolve-ip",
false);
String virtualHost = ParameterMap.getKeyedString(parameters,
"host-name", null);
if (isBlank(virtualHost)) {
if (res) {
virtualHost = ((InetAddress) iface.getAddress())
.getCanonicalHostName();
} else {
virtualHost = ((InetAddress) iface.getAddress())
.getHostAddress();
}
}
return virtualHost;
}
private String determineUserAgent(final Map<String, Object> parameters) {
String agent = ParameterMap.getKeyedString(parameters, "user-agent",
null);
if (isBlank(agent)) {
return "OpenNMS HttpMonitor";
}
return agent;
}
// String determineBasicAuthentication(final Map<String, Object> parameters) {
// String credentials = ParameterMap.getKeyedString(parameters,
// "basic-authentication", null);
//
// if (isNotBlank(credentials)) {
// credentials = new String(
// Base64.encodeBase64(credentials.getBytes()));
// } else {
//
// String user = ParameterMap.getKeyedString(parameters, "user", null);
//
// if (isBlank(user)) {
// credentials = null;
// } else {
// String passwd = ParameterMap.getKeyedString(parameters,
// "password", "");
// credentials = new String(
// Base64.encodeBase64((user + ":" + passwd).getBytes()));
// }
// }
//
// return credentials;
// }
private String determineHttpHeader(final Map<String, Object> parameters,
String key) {
return ParameterMap.getKeyedString(parameters, key, null);
}
}
}