diff --git a/pom.xml b/pom.xml index 2b7ad550b2..ef360bb90f 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ 2012 - 3.1.1.RELEASE + 3.2.3.RELEASE 4.11 true @@ -345,6 +345,13 @@ provided true + + org.springframework + spring-webmvc + ${spring.version} + provided + true + org.springframework spring-test @@ -540,5 +547,23 @@ 0.8.0 test + + org.javasimon + javasimon-spring + 3.3.0 + test + + + org.javasimon + javasimon-jdbc4 + 3.3.0 + test + + + org.javasimon + javasimon-console-embed + 3.3.0 + test + diff --git a/src/main/java/com/alibaba/druid/support/http/AbstractWebStatImpl.java b/src/main/java/com/alibaba/druid/support/http/AbstractWebStatImpl.java new file mode 100644 index 0000000000..f89bf02859 --- /dev/null +++ b/src/main/java/com/alibaba/druid/support/http/AbstractWebStatImpl.java @@ -0,0 +1,256 @@ +/* + * Copyright 1999-2011 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.druid.support.http; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import com.alibaba.druid.filter.stat.StatFilterContextListenerAdapter; +import com.alibaba.druid.support.http.stat.WebAppStat; +import com.alibaba.druid.support.http.stat.WebRequestStat; +import com.alibaba.druid.support.http.stat.WebSessionStat; +import com.alibaba.druid.util.DruidWebUtils; + +public class AbstractWebStatImpl { + + public final static int DEFAULT_MAX_STAT_SESSION_COUNT = 1000 * 1; + + protected WebAppStat webAppStat = null; + + protected boolean sessionStatEnable = true; + protected int sessionStatMaxCount = DEFAULT_MAX_STAT_SESSION_COUNT; + protected boolean createSession = false; + protected boolean profileEnable = false; + + protected String contextPath; + + protected String principalSessionName; + protected String principalCookieName; + protected String realIpHeader; + + protected WebStatFilterContextListener statFilterContextListener = new WebStatFilterContextListener(); + + public boolean isSessionStatEnable() { + return sessionStatEnable; + } + + public void setSessionStatEnable(boolean sessionStatEnable) { + this.sessionStatEnable = sessionStatEnable; + } + + public boolean isProfileEnable() { + return profileEnable; + } + + public void setProfileEnable(boolean profileEnable) { + this.profileEnable = profileEnable; + } + + public String getContextPath() { + return contextPath; + } + + public int getSessionStatMaxCount() { + return sessionStatMaxCount; + } + + public String getRequestURI(HttpServletRequest request) { + return request.getRequestURI(); + } + + public String getPrincipalSessionName() { + return principalSessionName; + } + + public String getPrincipalCookieName() { + return principalCookieName; + } + + public WebSessionStat getSessionStat(HttpServletRequest request) { + if (!isSessionStatEnable()) { + return null; + } + + WebSessionStat sessionStat = null; + String sessionId = getSessionId(request); + if (sessionId != null) { + sessionStat = webAppStat.getSessionStat(sessionId, true); + } + + if (sessionStat != null) { + long currentMillis = System.currentTimeMillis(); + + String userAgent = request.getHeader("user-agent"); + + if (sessionStat.getCreateTimeMillis() == -1L) { + HttpSession session = request.getSession(false); + + if (session != null) { + sessionStat.setCreateTimeMillis(session.getCreationTime()); + } else { + sessionStat.setCreateTimeMillis(currentMillis); + } + + webAppStat.computeUserAgent(userAgent); + webAppStat.incrementSessionCount(); + } + + sessionStat.setUserAgent(userAgent); + + String ip = getRemoteAddress(request); + + sessionStat.addRemoteAddress(ip); + } + + return sessionStat; + } + + protected String getRemoteAddress(HttpServletRequest request) { + String ip = null; + if (this.realIpHeader != null && this.realIpHeader.length() != 0) { + ip = request.getHeader(realIpHeader); + } + if (ip == null || ip.length() == 0) { + ip = DruidWebUtils.getRemoteAddr(request); + } + return ip; + } + + public String getSessionId(HttpServletRequest httpRequest) { + String sessionId = null; + + HttpSession session = httpRequest.getSession(createSession); + if (session != null) { + sessionId = session.getId(); + } + + return sessionId; + } + + public String getPrincipal(HttpServletRequest httpRequest) { + if (principalSessionName != null) { + HttpSession session = httpRequest.getSession(createSession); + if (session == null) { + return null; + } + + Object sessionValue = session.getAttribute(principalSessionName); + + if (sessionValue == null) { + return null; + } + + return sessionValue.toString(); + } + + if (principalCookieName != null && httpRequest.getCookies() != null) { + for (Cookie cookie : httpRequest.getCookies()) { + if (principalCookieName.equals(cookie.getName())) { + return cookie.getValue(); + } + } + } + + return null; + } + + public 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(); + } + } + } +} diff --git a/src/main/java/com/alibaba/druid/support/http/WebStatFilter.java b/src/main/java/com/alibaba/druid/support/http/WebStatFilter.java index b77c1725e9..bd7691a5c6 100644 --- a/src/main/java/com/alibaba/druid/support/http/WebStatFilter.java +++ b/src/main/java/com/alibaba/druid/support/http/WebStatFilter.java @@ -27,14 +27,11 @@ import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; -import javax.servlet.http.HttpSession; 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; @@ -55,39 +52,24 @@ import com.alibaba.druid.util.ServletPathMatcher; * @author wenshao * @author Zhangming Qi */ -public class WebStatFilter implements Filter { +public class WebStatFilter extends AbstractWebStatImpl implements Filter { - private final static Log LOG = LogFactory.getLog(WebStatFilter.class); + private final static Log LOG = LogFactory.getLog(WebStatFilter.class); - public final static String PARAM_NAME_PORFILE_ENABLE = "profileEnable"; - public final static String PARAM_NAME_SESSION_STAT_ENABLE = "sessionStatEnable"; - public final static String PARAM_NAME_SESSION_STAT_MAX_COUNT = "sessionStatMaxCount"; - public static final String PARAM_NAME_EXCLUSIONS = "exclusions"; - public static final String PARAM_NAME_PRINCIPAL_SESSION_NAME = "principalSessionName"; - public static final String PARAM_NAME_PRINCIPAL_COOKIE_NAME = "principalCookieName"; - public static final String PARAM_NAME_REAL_IP_HEADER = "realIpHeader"; + public final static String PARAM_NAME_PORFILE_ENABLE = "profileEnable"; + public final static String PARAM_NAME_SESSION_STAT_ENABLE = "sessionStatEnable"; + public final static String PARAM_NAME_SESSION_STAT_MAX_COUNT = "sessionStatMaxCount"; + public static final String PARAM_NAME_EXCLUSIONS = "exclusions"; + public static final String PARAM_NAME_PRINCIPAL_SESSION_NAME = "principalSessionName"; + public static final String PARAM_NAME_PRINCIPAL_COOKIE_NAME = "principalCookieName"; + public static final String PARAM_NAME_REAL_IP_HEADER = "realIpHeader"; - public final static int DEFAULT_MAX_STAT_SESSION_COUNT = 1000 * 100; - - private WebAppStat webAppStat = null; - private WebStatFilterContextListener statFilterContextListener = new WebStatFilterContextListener(); /** * PatternMatcher used in determining which paths to react to for a given request. */ - protected PatternMatcher pathMatcher = new ServletPathMatcher(); + protected PatternMatcher pathMatcher = new ServletPathMatcher(); - private Set excludesPattern; - - private boolean sessionStatEnable = true; - private int sessionStatMaxCount = DEFAULT_MAX_STAT_SESSION_COUNT; - private boolean createSession = false; - private boolean profileEnable = false; - - private String contextPath; - - private String principalSessionName; - private String principalCookieName; - private String realIpHeader; + private Set excludesPattern; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, @@ -203,94 +185,6 @@ public class WebStatFilter implements Filter { } } - public WebSessionStat getSessionStat(HttpServletRequest request) { - if (!isSessionStatEnable()) { - return null; - } - - WebSessionStat sessionStat = null; - String sessionId = getSessionId(request); - if (sessionId != null) { - sessionStat = webAppStat.getSessionStat(sessionId, true); - } - - if (sessionStat != null) { - long currentMillis = System.currentTimeMillis(); - - String userAgent = request.getHeader("user-agent"); - - if (sessionStat.getCreateTimeMillis() == -1L) { - HttpSession session = request.getSession(false); - - if (session != null) { - sessionStat.setCreateTimeMillis(session.getCreationTime()); - } else { - sessionStat.setCreateTimeMillis(currentMillis); - } - - webAppStat.computeUserAgent(userAgent); - webAppStat.incrementSessionCount(); - } - - sessionStat.setUserAgent(userAgent); - - String ip = getRemoteAddress(request); - - sessionStat.addRemoteAddress(ip); - } - - return sessionStat; - } - - protected String getRemoteAddress(HttpServletRequest request) { - String ip = null; - if (this.realIpHeader != null && this.realIpHeader.length() != 0) { - ip = request.getHeader(realIpHeader); - } - if (ip == null || ip.length() == 0) { - ip = DruidWebUtils.getRemoteAddr(request); - } - return ip; - } - - public String getSessionId(HttpServletRequest httpRequest) { - String sessionId = null; - - HttpSession session = httpRequest.getSession(createSession); - if (session != null) { - sessionId = session.getId(); - } - - return sessionId; - } - - public String getPrincipal(HttpServletRequest httpRequest) { - if (principalSessionName != null) { - HttpSession session = httpRequest.getSession(createSession); - if (session == null) { - return null; - } - - Object sessionValue = session.getAttribute(principalSessionName); - - if (sessionValue == null) { - return null; - } - - return sessionValue.toString(); - } - - if (principalCookieName != null && httpRequest.getCookies() != null) { - for (Cookie cookie : httpRequest.getCookies()) { - if (principalCookieName.equals(cookie.getName())) { - return cookie.getValue(); - } - } - } - - return null; - } - public boolean isExclusion(String requestURI) { if (excludesPattern == null) { return false; @@ -312,18 +206,6 @@ public class WebStatFilter implements Filter { return false; } - public String getRequestURI(HttpServletRequest request) { - return request.getRequestURI(); - } - - public String getPrincipalSessionName() { - return principalSessionName; - } - - public String getPrincipalCookieName() { - return principalCookieName; - } - @Override public void init(FilterConfig config) throws ServletException { { @@ -421,22 +303,6 @@ public class WebStatFilter implements Filter { } } - public boolean isSessionStatEnable() { - return sessionStatEnable; - } - - public void setSessionStatEnable(boolean sessionStatEnable) { - this.sessionStatEnable = sessionStatEnable; - } - - public boolean isProfileEnable() { - return profileEnable; - } - - public void setProfileEnable(boolean profileEnable) { - this.profileEnable = profileEnable; - } - public void setWebAppStat(WebAppStat webAppStat) { this.webAppStat = webAppStat; } @@ -445,104 +311,10 @@ public class WebStatFilter implements Filter { return webAppStat; } - public String getContextPath() { - return contextPath; - } - - public int getSessionStatMaxCount() { - return sessionStatMaxCount; - } - public WebStatFilterContextListener getStatFilterContextListener() { return statFilterContextListener; } - 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(); - } - } - } - public final static class StatHttpServletResponseWrapper extends HttpServletResponseWrapper implements HttpServletResponse { private int status; diff --git a/src/main/java/com/alibaba/druid/support/http/stat/WebAppStatManager.java b/src/main/java/com/alibaba/druid/support/http/stat/WebAppStatManager.java index 882eef923a..03ed23d37a 100644 --- a/src/main/java/com/alibaba/druid/support/http/stat/WebAppStatManager.java +++ b/src/main/java/com/alibaba/druid/support/http/stat/WebAppStatManager.java @@ -23,6 +23,7 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import com.alibaba.druid.stat.DruidDataSourceStatManager; +import com.alibaba.druid.util.StringUtils; public class WebAppStatManager { @@ -35,6 +36,22 @@ public class WebAppStatManager { public static WebAppStatManager getInstance() { return instance; } + + public synchronized WebAppStat getWebAppStat(String contextPath) { + Set stats = getWebAppStatSet(); + for (Object item : stats) { + if (item instanceof WebAppStat) { + WebAppStat stat = (WebAppStat) item; + if (StringUtils.equals(stat.getContextPath(), contextPath)) { + return stat; + } + } + } + + WebAppStat stat = new WebAppStat(contextPath); + this.addWebAppStatSet(stat); + return stat; + } public Set getWebAppStatSet() { if (webAppStatSet == null) { diff --git a/src/main/java/com/alibaba/druid/support/spring/mvc/StatHandlerInterceptor.java b/src/main/java/com/alibaba/druid/support/spring/mvc/StatHandlerInterceptor.java new file mode 100644 index 0000000000..ff29711ada --- /dev/null +++ b/src/main/java/com/alibaba/druid/support/spring/mvc/StatHandlerInterceptor.java @@ -0,0 +1,162 @@ +/* + * Copyright 1999-2011 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.druid.support.spring.mvc; + +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.ModelAndView; + +import com.alibaba.druid.filter.stat.StatFilterContext; +import com.alibaba.druid.support.http.AbstractWebStatImpl; +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.WebSessionStat; +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.DruidWebUtils; + +public class StatHandlerInterceptor extends AbstractWebStatImpl implements HandlerInterceptor, InitializingBean, DisposableBean { + + public StatHandlerInterceptor(){ + + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + final WebAppStat webAppStat = getWebAppStat(request); + String requestURI = getRequestURI(request); + + long startNano = System.nanoTime(); + long startMillis = System.currentTimeMillis(); + + WebRequestStat requestStat = new WebRequestStat(startNano, startMillis); + WebRequestStat.set(requestStat); + + WebSessionStat sessionStat = getSessionStat(request); + webAppStat.beforeInvoke(); + + 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); + } + } + + 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(); + } + + return true; + } + + public WebAppStat getWebAppStat(HttpServletRequest request) { + if (webAppStat != null) { + return webAppStat; + } + + ServletContext context = request.getSession().getServletContext(); + String contextPath = DruidWebUtils.getContextPath(context); + webAppStat = WebAppStatManager.getInstance().getWebAppStat(contextPath); + return webAppStat; + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, + ModelAndView modelAndView) throws Exception { + + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, + Exception error) throws Exception { + WebRequestStat requestStat = WebRequestStat.current(); + long endNano = System.nanoTime(); + requestStat.setEndNano(endNano); + + WebSessionStat sessionStat = getSessionStat(request); + WebURIStat uriStat = WebURIStat.current(); + + long nanos = endNano - requestStat.getStartNano(); + webAppStat.afterInvoke(null, nanos); + + if (sessionStat == null) { + sessionStat = getSessionStat(request); + if (sessionStat != null) { + sessionStat.beforeInvoke(); // 补偿 + } + } + + if (sessionStat != null) { + sessionStat.afterInvoke(error, nanos); + sessionStat.setPrincipal(getPrincipal(request)); + } + + if (uriStat != null) { + uriStat.afterInvoke(error, nanos); + } + + WebRequestStat.set(null); + + if (isProfileEnable()) { + Profiler.release(nanos); + + Map requestStatsMap = Profiler.getStatsMap(); + if (uriStat != null) { + uriStat.getProfiletat().record(requestStatsMap); + } + Profiler.removeLocal(); + } + } + + public String getRequestURI(HttpServletRequest request) { + return (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); + } + + @Override + public void afterPropertiesSet() throws Exception { + StatFilterContext.getInstance().addContextListener(statFilterContextListener); + } + + @Override + public void destroy() throws Exception { + StatFilterContext.getInstance().removeContextListener(statFilterContextListener); + } +} diff --git a/src/main/java/com/alibaba/druid/util/DruidWebUtils.java b/src/main/java/com/alibaba/druid/util/DruidWebUtils.java index f81752eee2..999e149922 100644 --- a/src/main/java/com/alibaba/druid/util/DruidWebUtils.java +++ b/src/main/java/com/alibaba/druid/util/DruidWebUtils.java @@ -34,7 +34,7 @@ public class DruidWebUtils { return ip; } - + private static String getContextPath_2_5(ServletContext context) { String contextPath = context.getContextPath(); @@ -56,4 +56,5 @@ public class DruidWebUtils { return null; } } + }