/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.monitor;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.geoserver.filters.GeoServerFilter;
import org.geoserver.monitor.RequestData.Status;
import org.geoserver.platform.GeoServerExtensions;
import org.geotools.util.logging.Logging;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
public class MonitorFilter implements GeoServerFilter {
static Logger LOGGER = Logging.getLogger("org.geoserver.monitor");
Monitor monitor;
MonitorRequestFilter requestFilter;
ExecutorService postProcessExecutor;
public MonitorFilter(Monitor monitor, MonitorRequestFilter requestFilter) {
this.monitor = monitor;
this.requestFilter = requestFilter;
postProcessExecutor = Executors.newFixedThreadPool(2);
if (monitor.isEnabled()) {
LOGGER.info("Monitor extension enabled");
}
else {
String msg ="Monitor extension disabled";
if (monitor.getConfig().getError() != null) {
msg += ": " + monitor.getConfig().getError().getLocalizedMessage();
}
LOGGER.info(msg);
}
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//check if enabled, and ignore non http requests
if (!monitor.isEnabled() || !(request instanceof HttpServletRequest)) {
chain.doFilter(request, response);
return;
}
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
if (requestFilter.filter(req)) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(req.getRequestURI() + " was filtered from monitoring");
}
//don't monitor this request
chain.doFilter(request, response);
return;
}
//start a new request
RequestData data = monitor.start();
data.setStartTime(new Date());
//fill in the initial data
data.setPath(req.getServletPath() + req.getPathInfo());
if (req.getQueryString() != null) {
data.setQueryString(URLDecoder.decode(req.getQueryString(), "UTF-8"));
}
data.setHttpMethod(req.getMethod());
data.setBodyContentLength(req.getContentLength());
data.setBodyContentType(req.getContentType());
String serverName = System.getProperty("http.serverName");
if (serverName == null) {
serverName = req.getServerName();
}
data.setHost(serverName);
data.setInternalHost(InternalHostname.get());
data.setRemoteAddr(getRemoteAddr(req));
data.setStatus(Status.RUNNING);
data.setHttpReferer(getHttpReferer(req));
if (SecurityContextHolder.getContext() != null
&& SecurityContextHolder.getContext().getAuthentication() != null) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Object principal = auth.getPrincipal();
if (principal != null) {
if (principal instanceof UserDetails) {
data.setRemoteUser(((UserDetails) principal).getUsername());
} else if (principal instanceof String) {
data.setRemoteUser((String) principal);
}
}
}
// fallback if the above method fails to get us a user
if (data.getRemoteUser() == null || data.getRemoteUser().isEmpty()) {
data.setRemoteUser(req.getRemoteUser());
}
data.setRemoteUserAgent(req.getHeader("user-agent"));
//wrap the request and response
request = new MonitorServletRequest(req, monitor.getConfig().getMaxBodySize());
response = new MonitorServletResponse(resp);
monitor.update();
//execute the request
Throwable error = null;
try {
chain.doFilter(request, response);
}
catch(Throwable t) {
error = t;
}
data = monitor.current();
data.setBody(getBody((MonitorServletRequest) request));
data.setBodyContentLength(((MonitorServletRequest)request).getBytesRead());
data.setResponseContentType(response.getContentType());
data.setResponseLength(((MonitorServletResponse)response).getContentLength());
data.setResponseStatus(((MonitorServletResponse)response).getStatus());
if (error != null) {
data.setStatus(Status.FAILED);
data.setErrorMessage(error.getLocalizedMessage());
data.setError(error);
}
if (data.getStatus() != Status.FAILED) {
data.setStatus(Status.FINISHED);
}
data.setEndTime(new Date());
data.setTotalTime(data.getEndTime().getTime() - data.getStartTime().getTime());
monitor.update();
data = monitor.current();
monitor.complete();
//post processing
postProcessExecutor.execute(new PostProcessTask(monitor, data, req, resp));
if (error != null) {
if (error instanceof RuntimeException) {
throw (RuntimeException)error;
}
else {
throw new RuntimeException(error);
}
}
}
public void destroy() {
postProcessExecutor.shutdown();
monitor.dispose();
}
String getRemoteAddr(HttpServletRequest req) {
String forwardedFor = req.getHeader("X-Forwarded-For");
if (forwardedFor != null) {
String[] ips = forwardedFor.split(", ");
return ips[0];
} else {
return req.getRemoteAddr();
}
}
String getHttpReferer(HttpServletRequest req) {
String referer = req.getHeader("Referer");
// "Referer" is in the HTTP spec, but "Referrer" is the correct English spelling.
// This falls back to the "correct" spelling if the specified one was not used.
if(referer==null)
referer = req.getHeader("Referrer");
return referer;
}
// Get the body and trim to the maximum allowable size if necessary
byte[] getBody(HttpServletRequest req) {
long maxBodyLength = monitor.config.getMaxBodySize();
if (maxBodyLength == 0) return null;
try {
byte[] body=((MonitorServletRequest)req).getBodyContent(); // TODO: trimming at this point may now be redundant
if(body!=null && maxBodyLength!=MonitorServletRequest.BODY_SIZE_UNBOUNDED && body.length>maxBodyLength)
body=Arrays.copyOfRange(body, 0, (int) maxBodyLength);
return body;
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "Could not read request body", ex);
return null;
}
}
static class PostProcessTask implements Runnable {
Monitor monitor;
RequestData data;
HttpServletRequest request;
HttpServletResponse response;
PostProcessTask(Monitor monitor, RequestData data, HttpServletRequest request, HttpServletResponse response) {
this.monitor = monitor;
this.data = data;
this.request = request;
this.response = response;
}
public void run() {
try {
List<RequestPostProcessor> pp = new ArrayList();
pp.add(new ReverseDNSPostProcessor());
pp.addAll(GeoServerExtensions.extensions(RequestPostProcessor.class));
for (RequestPostProcessor p : pp) {
try {
p.run(data, request, response);
}
catch(Exception e) {
LOGGER.log(Level.WARNING, "Post process task failed", e);
}
}
monitor.postProcessed(data);
}
finally {
monitor = null;
data = null;
request = null;
response = null;
}
}
}
}