码迷,mamicode.com
首页 > 其他好文 > 详细

Jetty Session

时间:2015-12-04 12:18:27      阅读:573      评论:0      收藏:0      [点我收藏+]

标签:

需求

  1. 系统管理员可以根据用户登录时的sessionid使用户的session变为无效状态,以达到强制其下线的目的。

  2. 要在集群环境中仍然有效,即用户在一个服务节点下线后,在其他节点也同样下线。

  3. 在集群环境session要共享且同步。

困难

  1. Servlet API中没有提供根据sessionid获取相应session的方法。

  2. Jetty默认不支持集群环境下的session共享

分析

  1. Servlet API中能够根据客户端传来的sessionid获取当前用户的session,在Servlet应用中也只能获取到当前用户的session。既然能够获取当前用户的session,那必然有根据sessionid获取session的方法,只不过出于安全的考虑没有暴露给Servlet应用。那这个根据sessionid获取session的方法肯定就在Servlet容器中。

  2. 在Jetty中提供了将session存储到数据库中,以达到在集群中共享的目的。

解决

根据sessionid获取session

对于Jetty,这个方法在org.eclipse.jetty.server.session.AbstractSessionManager中。
使用Jetty容器时,Servlet中的HttpServletRequest实例其实就是org.eclipse.jetty.server.Request实例

public class Request implements HttpServletRequest

org.eclipse.jetty.server.Request中有一个获取SessionManager的方法

    public SessionManager getSessionManager() {
        return _sessionManager;
    }

获取到的SessionManager根据使用的session方案不同,最终的实例类型会不一样,但最终的实例都有一个共同的基类org.eclipse.jetty.server.session.AbstractSessionManager,该类中定义根据sessionid获取session的方法

    public abstract AbstractSession getSession(String idInCluster);

以选用的Mongo存储方案为例,其实例为org.eclipse.jetty.nosql.mongodb.MongoSessionManager
技术分享

org.eclipse.jetty.nosql.NoSqlSessionManager中的实现为

    public AbstractSession getSession(String idInCluster)
    {
        NoSqlSession session = _sessions.get(idInCluster);
        __log.debug("getSession {} ", session );
        
        if (session==null)
        {
            //session not in this node‘s memory, load it
            session=loadSession(idInCluster);
            
            if (session!=null)
            {
                //session exists, check another request thread hasn‘t loaded it too
                NoSqlSession race=_sessions.putIfAbsent(idInCluster,session);
                if (race!=null)
                {
                    session.willPassivate();
                    session.clearAttributes();
                    session=race;
                }
                else
                    __log.debug("session loaded ", idInCluster);
                
                //check if the session we just loaded has actually expired, maybe while we weren‘t running
                if (getMaxInactiveInterval() > 0 && session.getAccessed() > 0 && ((getMaxInactiveInterval()*1000L)+session.getAccessed()) < System.currentTimeMillis())
                {
                    __log.debug("session expired ", idInCluster);
                    expire(idInCluster);
                    session = null;
                }
            }
            else
                __log.debug("session does not exist {}", idInCluster);
        }

        return session;
    }

如果要获取的session不在内存中,会尝试从数据库(Mongodb)中载入

//session not in this node‘s memory, load it
session=loadSession(idInCluster);

org.eclipse.jetty.nosql.NoSqlSessionManager#loadSession方法在org.eclipse.jetty.nosql.mongodb.MongoSessionManager中实现

    protected synchronized NoSqlSession loadSession(String clusterId) {
        DBObject o = _dbSessions.findOne(new BasicDBObject(__ID, clusterId));

        __log.debug("MongoSessionManager:id={} loaded={}", clusterId, o);
        if (o == null)
            return null;

        Boolean valid = (Boolean) o.get(__VALID);
        __log.debug("MongoSessionManager:id={} valid={}", clusterId, valid);
        if (valid == null || !valid)
            return null;

        try {
            Object version = o.get(getContextAttributeKey(__VERSION));
            Long created = (Long) o.get(__CREATED);
            Long accessed = (Long) o.get(__ACCESSED);

            NoSqlSession session = null;

            // get the session for the context
            DBObject attrs = (DBObject) getNestedValue(o, getContextKey());

            __log.debug("MongoSessionManager:attrs {}", attrs);
            if (attrs != null) {
                __log.debug("MongoSessionManager: session {} present for context {}", clusterId, getContextKey());
                //only load a session if it exists for this context
                session = new NoSqlSession(this, created, accessed, clusterId, version);

                for (String name : attrs.keySet()) {
                    //skip special metadata attribute which is not one of the actual session attributes
                    if (__METADATA.equals(name))
                        continue;

                    String attr = decodeName(name);
                    Object value = decodeValue(attrs.get(name));

                    session.doPutOrRemove(attr, value);
                    session.bindValue(attr, value);
                }
                session.didActivate();
            } else
                __log.debug("MongoSessionManager: session  {} not present for context {}", clusterId, getContextKey());

            return session;
        } catch (Exception e) {
            LOG.warn(e);
        }
        return null;
    }

遇到的问题

在Servlet应用中访问org.eclipse.jetty.server.Request时,抛出以下异常

java.lang.ClassNotFoundException: org.eclipse.jetty.server.Request
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at org.eclipse.jetty.webapp.WebAppClassLoader.findClass(WebAppClassLoader.java:510)
    at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:441)
    at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:403)

这是Jetty的类装载机制造成的。
在Jetty中,WebAppContext中的类即WEB-INF/classes/WEB-INF/lib/下的类是由org.eclipse.jetty.webapp.WebAppClassLoader装载的,在该ClassLoader的loadClass方法中有如下代码

    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        Class<?> c= findLoadedClass(name);
        ClassNotFoundException ex= null;
        boolean tried_parent= false;
        
        boolean system_class=_context.isSystemClass(name);
        boolean server_class=_context.isServerClass(name);
        
        if (system_class && server_class)
        {
            return null;
        }
        
        if (c == null && _parent!=null && (_context.isParentLoaderPriority() || system_class) && !server_class)
        {
            tried_parent= true;
            try
            {
                c= _parent.loadClass(name);
                if (LOG.isDebugEnabled())
                    LOG.debug("loaded " + c);
            }
            catch (ClassNotFoundException e)
            {
                ex= e;
            }
        }

        if (c == null)
        {
            try
            {
                c= this.findClass(name);
            }
            catch (ClassNotFoundException e)
            {
                ex= e;
            }
        }

        if (c == null && _parent!=null && !tried_parent && !server_class )
            c= _parent.loadClass(name);

        if (c == null && ex!=null)
            throw ex;

        if (resolve)
            resolveClass(c);

        if (LOG.isDebugEnabled())
            LOG.debug("loaded {} from {}",c,c==null?null:c.getClassLoader());
        
        return c;
    }

从以上代码中可以看出,WebAppClassLoader是拒绝装载server_class的,既然如此,那只需要将org.eclipse.jetty.server.Request排除在server_class之外就可以在应用中使用了,可以在WEB-INF/jetty-env.xml中进行配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
    <Set name="serverClasses">
        <Array type="java.lang.String">
            <Item>-org.eclipse.jetty.jmx.</Item>
            <Item>-org.eclipse.jetty.util.annotation.</Item>
            <Item>-org.eclipse.jetty.continuation.</Item>
            <Item>-org.eclipse.jetty.jndi.</Item>
            <Item>-org.eclipse.jetty.jaas.</Item>
            <Item>-org.eclipse.jetty.servlets.</Item>
            <Item>-org.eclipse.jetty.servlet.DefaultServlet</Item>
            <Item>-org.eclipse.jetty.jsp.</Item>
            <Item>-org.eclipse.jetty.servlet.listener.</Item>
            <Item>-org.eclipse.jetty.websocket.</Item>
            <Item>-org.eclipse.jetty.apache.</Item>
            <Item>-org.eclipse.jetty.util.log.</Item>
            <Item>-org.eclipse.jetty.servlet.ServletContextHandler.Decorator</Item>
            <Item>org.objectweb.asm.</Item>
            <Item>org.eclipse.jdt.</Item>
            <Item>-org.eclipse.jetty."</Item>
        </Array>
    </Set>
</Configure>

注意:虽然Jetty在启动时也会加载WEB-INF/jetty-web.xml,但以上配置写在WEB-INF/jetty-web.xml中是无效的,原因是在org.eclipse.jetty.webapp.JettyWebXmlConfiguration#configure方法中有这样一段代码,将你的配置覆盖掉

                finally
                {
                    if (old_server_classes != null)
                        context.setServerClasses(old_server_classes);
                }

说明:
system_class:字面,与系统相关的类,默认包含如下包下的类,但可通过WEB-INF/jetty-env.xml配置

   public final static String[] __dftSystemClasses =
   {
       "java.",                            // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
       "javax.",                           // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
       "org.xml.",                         // needed by javax.xml
       "org.w3c.",                         // needed by javax.xml
       "org.eclipse.jetty.jmx.",           // webapp cannot change jmx classes
       "org.eclipse.jetty.util.annotation.",  // webapp cannot change jmx annotations
       "org.eclipse.jetty.continuation.",  // webapp cannot change continuation classes
       "org.eclipse.jetty.jndi.",          // webapp cannot change naming classes
       "org.eclipse.jetty.jaas.",          // webapp cannot change jaas classes
       "org.eclipse.jetty.websocket.",     // webapp cannot change / replace websocket classes
       "org.eclipse.jetty.util.log.",      // webapp should use server log
       "org.eclipse.jetty.servlet.ServletContextHandler.Decorator", // for CDI / weld use
       "org.eclipse.jetty.servlet.DefaultServlet", // webapp cannot change default servlets
       "org.eclipse.jetty.jsp.JettyJspServlet", //webapp cannot change jetty jsp servlet
       "org.eclipse.jetty.servlets.AsyncGzipFilter" // special case for AsyncGzipFilter
   } ;

server_class: 字面,Jetty自身的类,默认包含 如下包下的类,但可通过WEB-INF/jetty-env.xml配置

   public final static String[] __dftServerClasses =
   {
       "-org.eclipse.jetty.jmx.",          // don‘t hide jmx classes
       "-org.eclipse.jetty.util.annotation.", // don‘t hide jmx annotation
       "-org.eclipse.jetty.continuation.", // don‘t hide continuation classes
       "-org.eclipse.jetty.jndi.",         // don‘t hide naming classes
       "-org.eclipse.jetty.jaas.",         // don‘t hide jaas classes
       "-org.eclipse.jetty.servlets.",     // don‘t hide jetty servlets
       "-org.eclipse.jetty.servlet.DefaultServlet", // don‘t hide default servlet
       "-org.eclipse.jetty.jsp.",          //don‘t hide jsp servlet
       "-org.eclipse.jetty.servlet.listener.", // don‘t hide useful listeners
       "-org.eclipse.jetty.websocket.",    // don‘t hide websocket classes from webapps (allow webapp to use >ones from system classloader)
       "-org.eclipse.jetty.apache.",       // don‘t hide jetty apache impls
       "-org.eclipse.jetty.util.log.",     // don‘t hide server log 
       "-org.eclipse.jetty.servlet.ServletContextHandler.Decorator", // don‘t hide CDI / weld interface  
       "org.objectweb.asm.",               // hide asm used by jetty
       "org.eclipse.jdt.",                 // hide jdt used by jetty
       "org.eclipse.jetty."                // hide other jetty classes
   } ;

_parentLoaderPriority: 当为true时,会先尝试使用WebAppClassLoader的父装载器装载类,如果不成功再使用WebAppClassLoaderWEB-INF/classes/WEB-INF/lib/中进行装载;当为false时,则优先使用WebAppClassLoader;该参数不影响本文讨论的问题,只影响类的搜寻路径的优先级,如在容器中有一个A1.0.jar的包,在WEB-INF/lib/是有一个A1.2.jar的包,如果_parentLoaderPriority==true会加载容器中的A1.0.jar,否则会加载WEB-INF/lib/A1.2.jar

将session存储到MongoDB中

先阅读这里的官方文档

原理:

  1. 创建的session会存储到MongoDB中

  2. 如果客户端传来的sessionid,则在创建之前,会先根据sessionid到MongoDB中查看是否已经存在

  3. 访问session(调用getAttribute)时,会到MongoDB中刷新session

注意:

  1. 通过sessionid获取到的session,无法通过setAttribute方法更改其属性值

org.eclipse.jetty.nosql.mongodb.MongoSessionManager中的一些关键代码

  1. 刷新session

    protected Object refresh(NoSqlSession session, Object version) {
        __log.debug("MongoSessionManager:refresh session {}", session.getId());

        // check if our in memory version is the same as what is on the disk
        if (version != null) {
            DBObject o = _dbSessions.findOne(new BasicDBObject(__ID, session.getClusterId()), _version_1);

            if (o != null) {
                Object saved = getNestedValue(o, getContextAttributeKey(__VERSION));

                if (saved != null && saved.equals(version)) {
                    __log.debug("MongoSessionManager:refresh not needed session {}", session.getId());
                    return version;
                }
                version = saved;
            }
        }

        // If we are here, we have to load the object
        DBObject o = _dbSessions.findOne(new BasicDBObject(__ID, session.getClusterId()));

        // If it doesn‘t exist, invalidate
        if (o == null) {
            __log.debug("MongoSessionManager:refresh:marking session {} invalid, no object", session.getClusterId());
            session.invalidate();
            return null;
        }

        // If it has been flagged invalid, invalidate
        Boolean valid = (Boolean) o.get(__VALID);
        if (valid == null || !valid) {
            __log.debug("MongoSessionManager:refresh:marking session {} invalid, valid flag {}", session.getClusterId(), valid);
            session.invalidate();
            return null;
        }

        // We need to update the attributes. We will model this as a passivate,
        // followed by bindings and then activation.
        session.willPassivate();
        try {
            DBObject attrs = (DBObject) getNestedValue(o, getContextKey());
            //if disk version now has no attributes, get rid of them
            if (attrs == null || attrs.keySet().size() == 0) {
                session.clearAttributes();
            } else {
                //iterate over the names of the attributes on the disk version, updating the value
                for (String name : attrs.keySet()) {
                    //skip special metadata field which is not one of the session attributes
                    if (__METADATA.equals(name))
                        continue;

                    String attr = decodeName(name);
                    Object value = decodeValue(attrs.get(name));

                    //session does not already contain this attribute, so bind it
                    if (session.getAttribute(attr) == null) {
                        session.doPutOrRemove(attr, value);
                        session.bindValue(attr, value);
                    } else //session already contains this attribute, update its value
                    {
                        session.doPutOrRemove(attr, value);
                    }

                }
                // cleanup, remove values from session, that don‘t exist in data anymore:
                for (String str : session.getNames()) {
                    if (!attrs.keySet().contains(encodeName(str))) {
                        session.doPutOrRemove(str, null);
                        session.unbindValue(str, session.getAttribute(str));
                    }
                }
            }

            /*
             * We are refreshing so we should update the last accessed time.
             */
            BasicDBObject key = new BasicDBObject(__ID, session.getClusterId());
            BasicDBObject sets = new BasicDBObject();
            // Form updates
            BasicDBObject update = new BasicDBObject();
            sets.put(__ACCESSED, System.currentTimeMillis());
            // Do the upsert
            if (!sets.isEmpty()) {
                update.put("$set", sets);
            }

            _dbSessions.update(key, update, false, false, WriteConcern.SAFE);

            session.didActivate();

            return version;
        } catch (Exception e) {
            LOG.warn(e);
        }

        return null;
    }

调用关系
技术分享

  1. 载入session

    protected synchronized NoSqlSession loadSession(String clusterId) {
        DBObject o = _dbSessions.findOne(new BasicDBObject(__ID, clusterId));

        __log.debug("MongoSessionManager:id={} loaded={}", clusterId, o);
        if (o == null)
            return null;

        Boolean valid = (Boolean) o.get(__VALID);
        __log.debug("MongoSessionManager:id={} valid={}", clusterId, valid);
        if (valid == null || !valid)
            return null;

        try {
            Object version = o.get(getContextAttributeKey(__VERSION));
            Long created = (Long) o.get(__CREATED);
            Long accessed = (Long) o.get(__ACCESSED);

            NoSqlSession session = null;

            // get the session for the context
            DBObject attrs = (DBObject) getNestedValue(o, getContextKey());

            __log.debug("MongoSessionManager:attrs {}", attrs);
            if (attrs != null) {
                __log.debug("MongoSessionManager: session {} present for context {}", clusterId, getContextKey());
                //only load a session if it exists for this context
                session = new NoSqlSession(this, created, accessed, clusterId, version);

                for (String name : attrs.keySet()) {
                    //skip special metadata attribute which is not one of the actual session attributes
                    if (__METADATA.equals(name))
                        continue;

                    String attr = decodeName(name);
                    Object value = decodeValue(attrs.get(name));

                    session.doPutOrRemove(attr, value);
                    session.bindValue(attr, value);
                }
                session.didActivate();
            } else
                __log.debug("MongoSessionManager: session  {} not present for context {}", clusterId, getContextKey());

            return session;
        } catch (Exception e) {
            LOG.warn(e);
        }
        return null;
    }

调用关系
技术分享

Jetty Session

标签:

原文地址:http://www.cnblogs.com/daojoo/p/5018617.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!