package ameba.feature.datasource; import com.alibaba.druid.filter.stat.StatFilterContext; import com.alibaba.druid.filter.stat.StatFilterContextListenerAdapter; import com.alibaba.druid.support.http.stat.WebAppStat; import com.alibaba.druid.support.http.stat.WebAppStatManager; import com.alibaba.druid.support.http.stat.WebRequestStat; import com.alibaba.druid.support.http.stat.WebURIStat; import com.alibaba.druid.support.profile.ProfileEntryKey; import com.alibaba.druid.support.profile.ProfileEntryReqStat; import com.alibaba.druid.support.profile.Profiler; import com.alibaba.druid.util.PatternMatcher; import com.alibaba.druid.util.ServletPathMatcher; import com.google.common.collect.Sets; import groovy.lang.Singleton; import org.apache.commons.lang3.StringUtils; import org.glassfish.jersey.server.ContainerRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.ws.rs.container.*; import javax.ws.rs.core.Configuration; import javax.ws.rs.core.Cookie; import javax.ws.rs.core.Response; import java.io.IOException; import java.util.Map; import java.util.Set; /** * @author icode * @since 0.1.6 */ @PreMatching @Singleton class WebStatFilter implements ContainerRequestFilter, ContainerResponseFilter { /** * Constant <code>PARAM_NAME_PORFILE_ENABLE="datasource.profileEnable"</code> */ public final static String PARAM_NAME_PORFILE_ENABLE = "datasource.profileEnable"; /** * Constant <code>PARAM_NAME_SESSION_STAT_MAX_COUNT="datasource.sessionStatMaxCount"</code> */ public final static String PARAM_NAME_SESSION_STAT_MAX_COUNT = "datasource.sessionStatMaxCount"; /** * Constant <code>PARAM_NAME_EXCLUSIONS="datasource.exclusions"</code> */ public static final String PARAM_NAME_EXCLUSIONS = "datasource.exclusions"; /** * Constant <code>PARAM_NAME_PRINCIPAL_COOKIE_NAME="datasource.principalCookieName"</code> */ public static final String PARAM_NAME_PRINCIPAL_COOKIE_NAME = "datasource.principalCookieName"; /** * Constant <code>PARAM_NAME_REAL_IP_HEADER="datasource.realIpHeader"</code> */ public static final String PARAM_NAME_REAL_IP_HEADER = "datasource.realIpHeader"; /** * Constant <code>DEFAULT_MAX_STAT_SESSION_COUNT=1000 * 100</code> */ public final static int DEFAULT_MAX_STAT_SESSION_COUNT = 1000 * 100; private static final Logger logger = LoggerFactory.getLogger(WebStatFilter.class); /** * PatternMatcher used in determining which paths to react to for a given request. */ protected static PatternMatcher pathMatcher = new ServletPathMatcher(); private static WebAppStat webAppStat = null; private static WebStatFilterContextListener statFilterContextListener = new WebStatFilterContextListener(); private static Set<String> excludesPattern; private static int sessionStatMaxCount = DEFAULT_MAX_STAT_SESSION_COUNT; private static boolean profileEnable = false; private static String contextPath; private static String principalCookieName; private static String realIpHeader; /** * <p>Constructor for WebStatFilter.</p> * * @param configuration a {@link javax.ws.rs.core.Configuration} object. */ @Inject public WebStatFilter(Configuration configuration) { if (webAppStat != null) { return; } { String exclusions = (String) configuration.getProperty(PARAM_NAME_EXCLUSIONS); if (exclusions != null && exclusions.trim().length() != 0) { excludesPattern = Sets.newHashSet(exclusions.split("\\s*,\\s*")); } } { String param = (String) configuration.getProperty(PARAM_NAME_PRINCIPAL_COOKIE_NAME); if (param != null) { param = param.trim(); if (param.length() != 0) { principalCookieName = param; } } } { String param = (String) configuration.getProperty(PARAM_NAME_PORFILE_ENABLE); if (param != null && param.trim().length() != 0) { param = param.trim(); if ("true".equals(param)) { profileEnable = true; } else if ("false".equals(param)) { profileEnable = false; } else { logger.error("WebStatFilter Parameter '" + PARAM_NAME_PORFILE_ENABLE + "' config error"); } } } { String param = (String) configuration.getProperty(PARAM_NAME_SESSION_STAT_MAX_COUNT); if (param != null && param.trim().length() != 0) { param = param.trim(); try { sessionStatMaxCount = Integer.parseInt(param); } catch (NumberFormatException e) { logger.error("WebStatFilter Parameter '" + PARAM_NAME_SESSION_STAT_MAX_COUNT + "' config error", e); } } } // realIpHeader { String param = (String) configuration.getProperty(PARAM_NAME_REAL_IP_HEADER); if (param != null) { param = param.trim(); if (param.length() != 0) { realIpHeader = param; } } } StatFilterContext.getInstance().addContextListener(statFilterContextListener); contextPath = "/"; if (webAppStat == null) { webAppStat = new WebAppStat(contextPath, sessionStatMaxCount); } WebAppStatManager.getInstance().addWebAppStatSet(webAppStat); } private WebURIStat getUriStat(String requestURI) { WebURIStat uriStat = webAppStat.getURIStat(requestURI, false); if (uriStat == null) { int index = requestURI.indexOf(";jsessionid="); if (index != -1) { requestURI = requestURI.substring(0, index); uriStat = webAppStat.getURIStat(requestURI, false); } } return uriStat; } /** * {@inheritDoc} */ @Override public void filter(ContainerRequestContext requestContext) throws IOException { String requestURI = getRequestURI(requestContext); if (isExclusion(requestURI)) { return; } long startNano = System.nanoTime(); long startMillis = System.currentTimeMillis(); WebRequestStat requestStat = new WebRequestStat(startNano, startMillis); WebRequestStat.set(requestStat); //WebSessionStat sessionStat = getSessionStat(httpRequest); webAppStat.beforeInvoke(); WebURIStat uriStat = getUriStat(requestURI); if (isProfileEnable()) { Profiler.initLocal(); Profiler.enter(requestURI, Profiler.PROFILE_TYPE_WEB); } // 第一次访问时,uriStat这里为null,是为了防止404攻击。 if (uriStat != null) { uriStat.beforeInvoke(); } // 第一次访问时,sessionId为null,如果缺省sessionCreate=false,sessionStat就为null。 // if (sessionStat != null) { // sessionStat.beforeInvoke(); // } } /** {@inheritDoc} */ @Override public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { String requestURI = getRequestURI(requestContext); if (isExclusion(requestURI) || WebRequestStat.current() == null) { return; } long endNano = System.nanoTime(); WebRequestStat.current().setEndNano(endNano); long nanos = endNano - WebRequestStat.current().getStartNano(); Throwable error = null; if (responseContext.getStatus() == Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) { if (Throwable.class.isInstance(responseContext.getEntity())) error = (Throwable) responseContext.getEntity(); } webAppStat.afterInvoke(error, nanos); WebURIStat uriStat = getUriStat(requestURI); if (uriStat == null) { int status = responseContext.getStatus(); if (status == Response.Status.NOT_FOUND.getStatusCode()) { String errorUrl = contextPath + "error_" + status; uriStat = webAppStat.getURIStat(errorUrl, true); } else { uriStat = webAppStat.getURIStat(requestURI, true); } if (uriStat != null) { uriStat.beforeInvoke(); // 补偿调用 } } if (uriStat != null) { uriStat.afterInvoke(error, nanos); } WebRequestStat.set(null); if (isProfileEnable()) { Profiler.release(nanos); Map<ProfileEntryKey, ProfileEntryReqStat> requestStatsMap = Profiler.getStatsMap(); if (uriStat != null) { uriStat.getProfiletat().record(requestStatsMap); } Profiler.removeLocal(); } } /** * <p>getRemoteAddress.</p> * * @param request a {@link javax.ws.rs.container.ContainerRequestContext} object. * @return a {@link java.lang.String} object. */ protected String getRemoteAddress(ContainerRequestContext request) { String ip = null; if (realIpHeader != null && realIpHeader.length() != 0) { ip = request.getHeaderString(realIpHeader); } if (StringUtils.isBlank(ip)) { ip = request.getHeaderString("x-forwarded-for"); if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeaderString("Proxy-Client-IP"); } if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeaderString("WL-Proxy-Client-IP"); } if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) { ip = "unknown"; } } return ip; } // public void destroy() { // StatFilterContext.getInstance().removeContextListener(statFilterContextListener); // // if (webAppStat != null) { // WebAppStatManager.getInstance().remove(webAppStat); // } // } /** * <p>getPrincipal.</p> * * @param httpRequest a {@link javax.ws.rs.container.ContainerRequestContext} object. * @return a {@link java.lang.String} object. */ public String getPrincipal(ContainerRequestContext httpRequest) { if (principalCookieName != null && httpRequest.getCookies().size() > 0) { Map<String, Cookie> cookies = httpRequest.getCookies(); for (Cookie cookie : cookies.values()) { if (principalCookieName.equals(cookie.getName())) { return cookie.getValue(); } } } return null; } /** * <p>isExclusion.</p> * * @param requestURI a {@link java.lang.String} object. * @return a boolean. */ public boolean isExclusion(String requestURI) { if (excludesPattern == null) { return false; } if (contextPath != null && requestURI.startsWith(contextPath)) { requestURI = requestURI.substring(contextPath.length()); if (!requestURI.startsWith("/")) { requestURI = "/" + requestURI; } } for (String pattern : excludesPattern) { if (pathMatcher.matches(pattern, requestURI)) { return true; } } return false; } /** * <p>getRequestURI.</p> * * @param request a {@link javax.ws.rs.container.ContainerRequestContext} object. * @return a {@link java.lang.String} object. */ public String getRequestURI(ContainerRequestContext request) { return ((ContainerRequest) request).getPath(true); } /** * <p>Getter for the field <code>principalCookieName</code>.</p> * * @return a {@link java.lang.String} object. */ public String getPrincipalCookieName() { return principalCookieName; } /** * <p>isProfileEnable.</p> * * @return a boolean. */ public boolean isProfileEnable() { return profileEnable; } /** * <p>Setter for the field <code>profileEnable</code>.</p> * * @param profileEnable a boolean. */ public void setProfileEnable(boolean profileEnable) { WebStatFilter.profileEnable = profileEnable; } /** * <p>Getter for the field <code>webAppStat</code>.</p> * * @return a {@link com.alibaba.druid.support.http.stat.WebAppStat} object. */ public WebAppStat getWebAppStat() { return webAppStat; } /** * <p>Setter for the field <code>webAppStat</code>.</p> * * @param webAppStat a {@link com.alibaba.druid.support.http.stat.WebAppStat} object. */ public void setWebAppStat(WebAppStat webAppStat) { WebStatFilter.webAppStat = webAppStat; } /** * <p>Getter for the field <code>contextPath</code>.</p> * * @return a {@link java.lang.String} object. */ public String getContextPath() { return contextPath; } /** * <p>Getter for the field <code>sessionStatMaxCount</code>.</p> * * @return a int. */ public int getSessionStatMaxCount() { return sessionStatMaxCount; } /** * <p>Getter for the field <code>statFilterContextListener</code>.</p> * * @return a {@link ameba.feature.datasource.WebStatFilter.WebStatFilterContextListener} object. */ public WebStatFilterContextListener getStatFilterContextListener() { return statFilterContextListener; } static class WebStatFilterContextListener extends StatFilterContextListenerAdapter { @Override public void addUpdateCount(int updateCount) { WebRequestStat reqStat = WebRequestStat.current(); if (reqStat != null) { reqStat.addJdbcUpdateCount(updateCount); } } @Override public void addFetchRowCount(int fetchRowCount) { WebRequestStat reqStat = WebRequestStat.current(); if (reqStat != null) { reqStat.addJdbcFetchRowCount(fetchRowCount); } } @Override public void executeBefore(String sql, boolean inTransaction) { WebRequestStat reqStat = WebRequestStat.current(); if (reqStat != null) { reqStat.incrementJdbcExecuteCount(); } } @Override public void executeAfter(String sql, long nanos, Throwable error) { WebRequestStat reqStat = WebRequestStat.current(); if (reqStat != null) { reqStat.addJdbcExecuteTimeNano(nanos); if (error != null) { reqStat.incrementJdbcExecuteErrorCount(); } } } @Override public void commit() { WebRequestStat reqStat = WebRequestStat.current(); if (reqStat != null) { reqStat.incrementJdbcCommitCount(); } } @Override public void rollback() { WebRequestStat reqStat = WebRequestStat.current(); if (reqStat != null) { reqStat.incrementJdbcRollbackCount(); } } @Override public void pool_connect() { WebRequestStat reqStat = WebRequestStat.current(); if (reqStat != null) { reqStat.incrementJdbcPoolConnectCount(); } } @Override public void pool_close(long nanos) { WebRequestStat reqStat = WebRequestStat.current(); if (reqStat != null) { reqStat.incrementJdbcPoolCloseCount(); } } @Override public void resultSet_open() { WebRequestStat reqStat = WebRequestStat.current(); if (reqStat != null) { reqStat.incrementJdbcResultSetOpenCount(); } } @Override public void resultSet_close(long nanos) { WebRequestStat reqStat = WebRequestStat.current(); if (reqStat != null) { reqStat.incrementJdbcResultSetCloseCount(); } } } }