tomcat集群时统计session与在线人数
2014-03-24 12:31
429 查看
tomcat集群时,原来通过HttpSessionListener实现类监听session的创建和销毁来统计在线人数的方法不再有效,因为不是每个人登陆都会在同一个tomcat服务器上,而在另一台tomcat上登陆的人的session是通过session复制创建的,而复制过程不会调用HttpSessionListener接口的方法,也一直没找着如何监听session复制的方法,所以就没法统计在线人了。
今天突然回想起tomcat下的manager应用上面就能看到session数和session的内容,于是本文的实现原理就是,做一个类似这样的servlet,此servlet把tomcat上负责管理应用的对象保存下来,供我任意使用。在tomcat上看应用的信息时,使用的是http://localhost:8080/manager/html/list这个路径,页面信息见下图:
于是把源码下来看看 (apache-tomcat-6.0.39-src),细看tomcat下webapps/manager/WEB-INF/web.xml文件配置,发现原来tomcat是通过org.apache.catalina.manager.ManagerServlet这个类来提供以上服务的,跟踪此类doGet方法代码
list
注意:context.getManager().findSessions()可以取得所有session,但这是个org.apache.catalina.Session[]数组,不是HttpSession[]数组,但这个Session接口里面有个getSession方法,返回结果正是HttpSession类型,没错,就是循环这个数组并调用其getSession方法就可以取得所有在线用户了
上面的Session[]数组是从context对象里面来的,而context是从host对象来的,host是个初始值为NULL的成员变量,是什么时候赋上值的?是在init方法执行前,setWrapper方法执行时赋的值,请看setWrapper方法代码
public class HostManagerServlet
extends HttpServlet implements ContainerServlet
setWrapper会在初始化时被调用,怎么实现的,首先看web.xml中对此servlet的配置,没什么特别,我们可以发散一下思维,struts2里面action如何能自动注入request对象?Spring如何让service监听事件?答案是一样的,那就是让你的类实现某个接口,你要的东西就给你了,对的,这里也一样,此servlet实现了ContainerServlet接口,初始的时候setWrapper方法才会被调用。
是JAVA新手的看这里,我提出上面这些问题,不是想卖什么关子,只是想启发JAVA初学者们,当某天你们做设计时,可以参考这种方法,一句概括就是:只要你实现我的接口,我就可以让你做某事,而不需要任何额外的配置。当然这种设计的缺点就是入侵、偶合。举个简单的应用场景:每天晚上,我用一个定时器通过Spring搜索所有实现了“GarbageCleaner”接口的service bean,并调用其clean方法清理对应模块的垃圾数据,那么任何模块的service只要实现了此接口,就会被调用。
回到正题,我也自已写个servlet并且实ContainerServlet接口吧,使用静态方法取得所有的session,具体代码如下:
在web.xml配置一下,然后启动应用,访问之,结果出现异常,是一个安全异常:TomcatWrapperServlet is privileged and cannot be loaded by this web application(想想如下),说我的类是个特权类,不能被普通的web应用加载,为何manager这个应用又可以呢?把manager/META-INF/context.xml复制到我的应用,再加载,再访问,一切搞定,此文件内容只有一句
Xml代码
message Error allocating a servlet instance
description The server encountered an internal error that prevented it from fulfilling this request.
exception
root cause
note The full stack trace of the root cause is available in the Apache Tomcat/6.0.37 logs.
今天突然回想起tomcat下的manager应用上面就能看到session数和session的内容,于是本文的实现原理就是,做一个类似这样的servlet,此servlet把tomcat上负责管理应用的对象保存下来,供我任意使用。在tomcat上看应用的信息时,使用的是http://localhost:8080/manager/html/list这个路径,页面信息见下图:
于是把源码下来看看 (apache-tomcat-6.0.39-src),细看tomcat下webapps/manager/WEB-INF/web.xml文件配置,发现原来tomcat是通过org.apache.catalina.manager.ManagerServlet这个类来提供以上服务的,跟踪此类doGet方法代码
// --------------------------------------------------------- Public Methods /** * Process a GET request for the specified resource. * * @param request * The servlet request we are processing * @param response * The servlet response we are creating * * @exception IOException * if an input/output error occurs * @exception ServletException * if a servlet-specified error occurs */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Identify the request parameters that we need // 取得访问路径, String command = request.getPathInfo(); String path = request.getParameter("path"); String deployPath = request.getParameter("deployPath"); String deployConfig = request.getParameter("deployConfig"); String deployWar = request.getParameter("deployWar"); // Prepare our output writer to generate the response message response.setContentType("text/html; charset=" + Constants.CHARSET); String message = ""; // Process the requested command if (command == null || command.equals("/")) { } else if (command.equals("/deploy")) { message = deployInternal(deployConfig, deployPath, deployWar); // 找到了,就是这个路径,往下看list方法 } else if (command.equals("/list")) { } else if (command.equals("/reload")) { message = reload(path); } else if (command.equals("/undeploy")) { message = undeploy(path); } else if (command.equals("/expire")) { message = expireSessions(path, request); } else if (command.equals("/sessions")) { try { doSessions(path, request, response); return; } catch (Exception e) { log("HTMLManagerServlet.sessions[" + path + "]", e); message = sm .getString("managerServlet.exception", e.toString()); } } else if (command.equals("/start")) { message = start(path); } else if (command.equals("/stop")) { message = stop(path); } else if (command.equals("/findleaks")) { message = findleaks(); } else { message = sm.getString("managerServlet.unknownCommand", command); } // 就是这个方法生成上面的那个页面 list(request, response, message); }
list
/** * Render a HTML list of the currently active Contexts in our virtual host, * and memory and server status information. * * @param request * The request * @param response * The response * @param message * a message to display */ public void list(HttpServletRequest request, HttpServletResponse response, String message) throws IOException { if (debug >= 1) log("list: Listing contexts for virtual host '" + host.getName() + "'"); PrintWriter writer = response.getWriter(); // HTML Header Section writer.print(Constants.HTML_HEADER_SECTION); // Body Header Section Object[] args = new Object[2]; args[0] = request.getContextPath(); args[1] = sm.getString("htmlManagerServlet.title"); writer.print(MessageFormat.format(Constants.BODY_HEADER_SECTION, args)); // Message Section args = new Object[3]; args[0] = sm.getString("htmlManagerServlet.messageLabel"); if (message == null || message.length() == 0) { args[1] = "OK"; } else { args[1] = RequestUtil.filter(message); } writer.print(MessageFormat.format(Constants.MESSAGE_SECTION, args)); // Manager Section args = new Object[9]; args[0] = sm.getString("htmlManagerServlet.manager"); args[1] = response.encodeURL(request.getContextPath() + "/html/list"); args[2] = sm.getString("htmlManagerServlet.list"); args[3] = response.encodeURL(request.getContextPath() + "/" + sm.getString("htmlManagerServlet.helpHtmlManagerFile")); args[4] = sm.getString("htmlManagerServlet.helpHtmlManager"); args[5] = response.encodeURL(request.getContextPath() + "/" + sm.getString("htmlManagerServlet.helpManagerFile")); args[6] = sm.getString("htmlManagerServlet.helpManager"); args[7] = response.encodeURL(request.getContextPath() + "/status"); args[8] = sm.getString("statusServlet.title"); writer.print(MessageFormat.format(Constants.MANAGER_SECTION, args)); // Apps Header Section args = new Object[6]; args[0] = sm.getString("htmlManagerServlet.appsTitle"); args[1] = sm.getString("htmlManagerServlet.appsPath"); args[2] = sm.getString("htmlManagerServlet.appsName"); args[3] = sm.getString("htmlManagerServlet.appsAvailable"); args[4] = sm.getString("htmlManagerServlet.appsSessions"); args[5] = sm.getString("htmlManagerServlet.appsTasks"); writer.print(MessageFormat.format(APPS_HEADER_SECTION, args)); // Apps Row Section // Create sorted map of deployed applications context paths. // host就当是当前的tomcat吧,那么contexts就此tomcat下的所有应用 Container children[] = host.findChildren(); String contextPaths[] = new String[children.length]; // 循环每个应用 for (int i = 0; i < children.length; i++) // 应用名称 contextPaths[i] = children[i].getName(); TreeMap sortedContextPathsMap = new TreeMap(); for (int i = 0; i < contextPaths.length; i++) { // 应用部署路径 String displayPath = contextPaths[i]; sortedContextPathsMap.put(displayPath, contextPaths[i]); } String appsStart = sm.getString("htmlManagerServlet.appsStart"); String appsStop = sm.getString("htmlManagerServlet.appsStop"); String appsReload = sm.getString("htmlManagerServlet.appsReload"); String appsUndeploy = sm.getString("htmlManagerServlet.appsUndeploy"); String appsExpire = sm.getString("htmlManagerServlet.appsExpire"); Iterator iterator = sortedContextPathsMap.entrySet().iterator(); boolean isHighlighted = true; boolean isDeployed = true; String highlightColor = null; while (iterator.hasNext()) { // Bugzilla 34818, alternating row colors isHighlighted = !isHighlighted; if (isHighlighted) { highlightColor = "#C3F3C3"; } else { highlightColor = "#FFFFFF"; } Map.Entry entry = (Map.Entry) iterator.next(); String displayPath = (String) entry.getKey(); String contextPath = (String) entry.getValue(); Context context = (Context) host.findChild(contextPath); if (displayPath.equals("")) { displayPath = "/"; } if (context != null) { try { isDeployed = isDeployed(contextPath); } catch (Exception e) { // Assume false on failure for safety isDeployed = false; } args = new Object[7]; args[0] = URL_ENCODER.encode(contextPath + "/"); args[1] = RequestUtil.filter(displayPath); if (context.getDisplayName() == null) { args[2] = " "; } else { args[2] = RequestUtil.filter(context.getDisplayName()); } managerServlet // 应用是否已启动 args[3] = new Boolean(context.getAvailable()); args[4] = response.encodeURL(request.getContextPath() + "/html/sessions?path=" + URL_ENCODER.encode(displayPath)); if (context.getManager() != null) { args[5] = new Integer(context.getManager() .getActiveSessions()); } else { args[5] = new Integer(0); } args[6] = highlightColor; //打印出一行关于此应用的信息,应用的URL,当前状态,session数等,具体见上图 writer.print(MessageFormat.format(APPS_ROW_DETAILS_SECTION, args)); args = new Object[14]; args[0] = response .encodeURL(request.getContextPath() + "/html/start?path=" + URL_ENCODER.encode(displayPath)); args[1] = appsStart; args[2] = response.encodeURL(request.getContextPath() + "/html/stop?path=" + URL_ENCODER.encode(displayPath)); args[3] = appsStop; args[4] = response.encodeURL(request.getContextPath() + "/html/reload?path=" + URL_ENCODER.encode(displayPath)); args[5] = appsReload; args[6] = response.encodeURL(request.getContextPath() + "/html/undeploy?path=" + URL_ENCODER.encode(displayPath)); args[7] = appsUndeploy; args[8] = response.encodeURL(request.getContextPath() + "/html/expire?path=" + URL_ENCODER.encode(displayPath)); args[9] = appsExpire; args[10] = sm.getString("htmlManagerServlet.expire.explain"); Manager manager = context.getManager(); if (manager == null) { args[11] = sm.getString("htmlManagerServlet.noManager"); } else { args[11] = new Integer(context.getManager() .getMaxInactiveInterval() / 60); } args[12] = sm.getString("htmlManagerServlet.expire.unit"); args[13] = highlightColor; if (context.getPath().equals(this.context.getPath())) { writer.print(MessageFormat.format( MANAGER_APP_ROW_BUTTON_SECTION, args)); } else if (context.getAvailable() && isDeployed) { writer.print(MessageFormat.format( STARTED_DEPLOYED_APPS_ROW_BUTTON_SECTION, args)); } else if (context.getAvailable() && !isDeployed) { writer.print(MessageFormat.format( STARTED_NONDEPLOYED_APPS_ROW_BUTTON_SECTION, args)); } else if (!context.getAvailable() && isDeployed) { writer.print(MessageFormat.format( STOPPED_DEPLOYED_APPS_ROW_BUTTON_SECTION, args)); } else { writer.print(MessageFormat.format( STOPPED_NONDEPLOYED_APPS_ROW_BUTTON_SECTION, args)); } } } // Deploy Section args = new Object[7]; args[0] = sm.getString("htmlManagerServlet.deployTitle"); args[1] = sm.getString("htmlManagerServlet.deployServer"); args[2] = response.encodeURL(request.getContextPath() + "/html/deploy"); args[3] = sm.getString("htmlManagerServlet.deployPath"); args[4] = sm.getString("htmlManagerServlet.deployConfig"); args[5] = sm.getString("htmlManagerServlet.deployWar"); args[6] = sm.getString("htmlManagerServlet.deployButton"); writer.print(MessageFormat.format(DEPLOY_SECTION, args)); args = new Object[4]; args[0] = sm.getString("htmlManagerServlet.deployUpload"); args[1] = response.encodeURL(request.getContextPath() + "/html/upload"); args[2] = sm.getString("htmlManagerServlet.deployUploadFile"); args[3] = sm.getString("htmlManagerServlet.deployButton"); writer.print(MessageFormat.format(UPLOAD_SECTION, args)); // Diagnostics section args = new Object[5]; args[0] = sm.getString("htmlManagerServlet.diagnosticsTitle"); args[1] = sm.getString("htmlManagerServlet.diagnosticsLeak"); args[2] = response.encodeURL(request.getContextPath() + "/html/findleaks"); args[3] = sm.getString("htmlManagerServlet.diagnosticsLeakWarning"); args[4] = sm.getString("htmlManagerServlet.diagnosticsLeakButton"); writer.print(MessageFormat.format(DIAGNOSTICS_SECTION, args)); // Server Header Section args = new Object[7]; args[0] = sm.getString("htmlManagerServlet.serverTitle"); args[1] = sm.getString("htmlManagerServlet.serverVersion"); args[2] = sm.getString("htmlManagerServlet.serverJVMVersion"); args[3] = sm.getString("htmlManagerServlet.serverJVMVendor"); args[4] = sm.getString("htmlManagerServlet.serverOSName"); args[5] = sm.getString("htmlManagerServlet.serverOSVersion"); args[6] = sm.getString("htmlManagerServlet.serverOSArch"); writer.print(MessageFormat .format(Constants.SERVER_HEADER_SECTION, args)); // Server Row Section args = new Object[6]; args[0] = ServerInfo.getServerInfo(); args[1] = System.getProperty("java.runtime.version"); args[2] = System.getProperty("java.vm.vendor"); args[3] = System.getProperty("os.name"); args[4] = System.getProperty("os.version"); args[5] = System.getProperty("os.arch"); writer.print(MessageFormat.format(Constants.SERVER_ROW_SECTION, args)); // HTML Tail Section writer.print(Constants.HTML_TAIL_SECTION); // Finish up the response writer.flush(); writer.close(); }
注意:context.getManager().findSessions()可以取得所有session,但这是个org.apache.catalina.Session[]数组,不是HttpSession[]数组,但这个Session接口里面有个getSession方法,返回结果正是HttpSession类型,没错,就是循环这个数组并调用其getSession方法就可以取得所有在线用户了
上面的Session[]数组是从context对象里面来的,而context是从host对象来的,host是个初始值为NULL的成员变量,是什么时候赋上值的?是在init方法执行前,setWrapper方法执行时赋的值,请看setWrapper方法代码
public class HostManagerServlet
extends HttpServlet implements ContainerServlet
/** * Set the Wrapper with which we are associated. * * @param wrapper The new wrapper */ public void setWrapper(Wrapper wrapper) { //这里所有需要的对象都有了,其实下面我们需要拿到wrapper就够了 this.wrapper = wrapper; if (wrapper == null) { context = null; host = null; engine = null; } else { context = (Context) wrapper.getParent(); host = (Host) context.getParent(); engine = (Engine) host.getParent(); } // Retrieve the MBean server mBeanServer = Registry.getRegistry(null, null).getMBeanServer(); }
setWrapper会在初始化时被调用,怎么实现的,首先看web.xml中对此servlet的配置,没什么特别,我们可以发散一下思维,struts2里面action如何能自动注入request对象?Spring如何让service监听事件?答案是一样的,那就是让你的类实现某个接口,你要的东西就给你了,对的,这里也一样,此servlet实现了ContainerServlet接口,初始的时候setWrapper方法才会被调用。
是JAVA新手的看这里,我提出上面这些问题,不是想卖什么关子,只是想启发JAVA初学者们,当某天你们做设计时,可以参考这种方法,一句概括就是:只要你实现我的接口,我就可以让你做某事,而不需要任何额外的配置。当然这种设计的缺点就是入侵、偶合。举个简单的应用场景:每天晚上,我用一个定时器通过Spring搜索所有实现了“GarbageCleaner”接口的service bean,并调用其clean方法清理对应模块的垃圾数据,那么任何模块的service只要实现了此接口,就会被调用。
回到正题,我也自已写个servlet并且实ContainerServlet接口吧,使用静态方法取得所有的session,具体代码如下:
package manager.session.http.servlet; import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.catalina.ContainerServlet; import org.apache.catalina.Context; import org.apache.catalina.Session; import org.apache.catalina.Wrapper; public class TomcatWrapperServlet extends HttpServlet implements ContainerServlet { private static final long serialVersionUID = 1L; // 弄个静态变量,初始化后就记下来,以备随时使用 private static Wrapper wrapper = null; public Wrapper getWrapper() { return wrapper; } public void setWrapper(Wrapper wrapper) { TomcatWrapperServlet.wrapper = wrapper; } // doGet不做任何事情,只需要接收第一次请求,触发初始动作就完成它的使命了 @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().println("Hello world!"); resp.getWriter().flush(); resp.getWriter().close(); } // 初始化后可通过此静态方法取得所有session public static Map<String, HttpSession> fillSessions() { if (wrapper == null) {// 没有初始化 throw new RuntimeException( "本servlet未被初始化,您必须先通过URL访问本servlet后,才可以调用这个方法"); } Map<String, HttpSession> sessions = new LinkedHashMap<String, HttpSession>(); // 取得本应用 Context context = (Context) wrapper.getParent(); // 取得Session[]数组 Session[] temps = context.getManager().findSessions(); if (temps != null && temps.length > 0) { for (int j = 0; j < temps.length; j++) { // Map<sessionId,session> sessions.put(temps[j].getSession().getId(), temps[j] .getSession()); } } return sessions; } }
在web.xml配置一下,然后启动应用,访问之,结果出现异常,是一个安全异常:TomcatWrapperServlet is privileged and cannot be loaded by this web application(想想如下),说我的类是个特权类,不能被普通的web应用加载,为何manager这个应用又可以呢?把manager/META-INF/context.xml复制到我的应用,再加载,再访问,一切搞定,此文件内容只有一句
Xml代码
<Context antiResourceLocking="false" privileged="true" />
HTTP Status 500 - Error allocating a servlet instance
type Exception reportmessage Error allocating a servlet instance
description The server encountered an internal error that prevented it from fulfilling this request.
exception
javax.servlet.ServletException: Error allocating a servlet instance org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103) org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293) org.apache.coyote.http11.Http11AprProcessor.process(Http11AprProcessor.java:879) org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:617) org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1760) java.lang.Thread.run(Thread.java:722)
root cause
java.lang.SecurityException: Servlet of class manager.session.http.servlet.TomcatWrapperServlet is privileged and cannot be loaded by this web application org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103) org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293) org.apache.coyote.http11.Http11AprProcessor.process(Http11AprProcessor.java:879) org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:617) org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1760) java.lang.Thread.run(Thread.java:722)
note The full stack trace of the root cause is available in the Apache Tomcat/6.0.37 logs.
Apache Tomcat/6.0.37
来自:http://www.jspspace.com/ResearchTopics/Art-1757-17.html相关文章推荐
- tomcat集群时统计session与在线人数
- tomcat集群时统计session与在线人数
- tomcat集群时统计session与在线人数
- 用HttpSessionListener与HttpSessionBindingListener实现在线人数统计
- 利用HttpSessionListener实现网站在线人数统计功能
- 利用文件属性结合Session实现在线人数统计
- JavaWeb中的Session、SessionListener、在线人数统计
- HttpSessionListener统计在线人数 [转]
- Session、SessionListener、在线人数统计
- 关于jsp内置对象Session和Cookie使用的肤浅理解,如何使用Session来统计网站在线人数
- 好记性不如烂笔头44-javaWeb中用Session统计在线人数(8)
- session统计在线人数,查看游客列表,登录人列表
- 依赖于session的在线人数统计
- 利用HttpSessionListener实现网站在线人数统计功能
- Session实现统计在线人数
- 使用HttpSessionListener统计在线人数
- asp.net 在线人数统计,页面访问量 Application ,session
- 在线人数统计session管理
- ASP.NET中利用Application和Session统计在线人数、历史访问量
- 在线人数统计,解决了关闭浏览器窗口,释放session的问题