Jetty Session

  • 需求
  • 困难
  • 分析
  • 解决
    • 根据sessionid获取session

      • 遇到的问题
    • 将session存储到MongoDB中

需求

  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;
    }

调用关系

时间: 2024-10-13 14:09:46

Jetty Session的相关文章

Jetty Session Persistence By Redis

Copy jar 到$JETTY_HOME/lib/ext目录下 -rw-rw-r--. 1 conversant conversant 100193 Sep 11 17:34 commons-pool-1.5.5.jar -rw-rw-r--. 1 conversant conversant 228268 Sep 11 17:34 jackson-core-asl-1.9.3.jar -rw-rw-r--. 1 conversant conversant 773019 Sep 11 17:34

Jetty总览

Jetty入门 基本功能介绍 配置概览-怎么配置Jetty 配置概览-需要配置什么 Jetty配置 部署到Jetty 配置上下文 配置连接器 配置安全 配置JSP支持 Jetty管理指导 启动Jetty Session管理 配置JNDI 注解 JMX SPDY ALPN NPN FastCGI支持 打包Servlets.Filters和Handlers Jetty Runner Setuid 优化Jetty Jetty日志 Jetty开发指导 Maven和Jetty Ant和Jetty Hand

Jetty9.2.2集群Session共享

针对Jetty就不过多的介绍了,自行研究去吧! 1.准备环境 MySQL数据库:下载地址:自行百度. jetty-distribution-9.2.2:下载地址:http://download.eclipse.org/jetty/ 2.配置方式 在JETTY_HOME/etc目录下找到jetty.xml文件.添加如下配置: <!-- ========================== session mysql demo ===================================

jetty热部署,持久化session,jetty-maven插件配置

持久化session 背景 使用maven管理项目,使用jetty插件启动项目,虽然jetty是热部署的,但是没有配置的jetty并不算真正的热部署.因为在没有配置前每次热部署都会把session丢了.导致测试期间的数据丢失,重来一遍很麻烦. 本人使用的是jetty-maven-plugin,关于这个插件的一些基本配置就不说了,网上很多. 配置代码pom <plugin> <groupId>org.mortbay.jetty</groupId> <artifact

Jetty集群配置Session存储到MySQL、MongoDB

在Web开发中,Session表示HTTP服务器与客户端(例如浏览器)的"会话",每个客户端会有其对应的Session保存在服务器端,通常用来保存和客户端关联的一些信息,例如是否登录.购物车等. Session一般情况下是保存在服务器内存中.如果服务器重启,Session就会丢失.另外,如果是集群环境,一个Web应用部署在多台服务器上,一个用户多次请求可能会由不同的服务器来处理,Session如果保存在各自的服务器上,就无法共享了. 针对这个问题,Jetty服务器提供了用于集群环境下的

转:通过Spring Session实现新一代的Session管理

长期以来,session管理就是企业级Java中的一部分,以致于我们潜意识就认为它是已经解决的问题,在最近的记忆中,我们没有看到这个领域有很大的革新. 但是,现代的趋势是微服务以及可水平扩展的原生云应用(cloud native application),它们会挑战过去20多年来我们设计和构建session管理器时的前提假设,并且暴露了现代化session管理器的不足. 本文将会阐述最近发布的Spring Session API如何帮助我们克服眼下session管理方式中的一些不足,在企业级Ja

基于ZooKeeper的分布式Session实现(转)

1.   认识ZooKeeper ZooKeeper—— “动物园管理员”.动物园里当然有好多的动物,游客可以根据动物园提供的向导图到不同的场馆观赏各种类型的动物,而不是像走在原始丛林里,心惊胆颤的被动 物所观赏.为了让各种不同的动物呆在它们应该呆的地方,而不是相互串门,或是相互厮杀,就需要动物园管理员按照动物的各种习性加以分类和管理,这样我们才 能更加放心安全的观赏动物.回到我们企业级应用系统中,随着信息化水平的不断提高,我们的企业级系统变得越来越庞大臃肿,性能急剧下降,客户抱怨频频.拆 分系

Jetty 9嵌入式开发

官方网址:http://www.eclipse.org/jetty/ 下载地址:http://download.eclipse.org/jetty/stable-9/dist/ 文档网址:http://www.eclipse.org/jetty/documentation/ 当前Jetty网址上推荐使用的稳定版本:Jetty9.0. 介绍 Jetty9内容位于http://www.eclipse.org/jetty/documentation. 直接链接: http://www.eclipse.

Jetty和Maven HelloWorld

ApacheMaven是一个软件项目管理和理解工具.基于项目对象模型(POM)内容,Maven能够通过信息中心管理一个项目构建.报告和文档.它是一个理想的工具用来构建Web应用项目.这项目可以使用Jetty Maven插件在部署模式下运行Web应用. 你能使用Maven来构建嵌入式Jetty应用程序和标准的基于Web应用. 为了理解使用Jetty构建和运行的基本操作,首先阅读: 1) Jetty HelloWorld教程 http://wiki.eclipse.org/Jetty/Tutoria