• 搭建Web服务必须要知道的几个概念


    1. servlet基础概念

    1. 就是一个运行在WEB服务器上的小的Java程序,用来接收和响应从客户端发送过来的请求,通常使用HTTP协议。用户通过单击某个链接或者直接在浏览器的地址栏中输入URL来访问,Web服务器接收到该请求后交给Servlet容器。Servlet容器实例化Servlet,调用Servlet的一个特定方法对请求进行处理, 并产生一个响应。这个响应由Servlet容器返回给Web服务器,Web服务器包装这个响应,以HTTP响应的形式发送给Web浏览器.

    2. 接口servlet ———> 通用GenericServlet ————> HttpServlet

    3. servlet的生命周期

      • 何时创建:用户第一次访问时创建。但是可以配置在服务器启动的时候就去创建servlet
      • 何时销毁:当项目从服务器中移除或者关闭服务器的时候

      用户第一次访问Servlet的时候,服务器会创建一个Servlet的实例,那么Servlet中init方法就会执行.任何一次请求服务器都会创建一个新的线程访问Servlet中的service的方法.在service方法内部根据请求的方式的不同调用doXXX的方法.(get请求调用doGet,post请求调用doPost).当Servlet中服务器中移除掉,或者关闭服务器,Servlet的实例就会被销毁,那么destroy方法就会执行。

    4. ServletContext是一个域对象,作用范围是整个web工程,Servlet容器在启动时会加载Web应用,并为每个Web应用创建唯一的ServletContext对象。它可以获取全局初始化参数,作为域对象存取数据,读取文件等。在springboot中可以监听ServletContext的创建销毁,完成项目启动时要初始化的一些工作。

    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    import javax.servlet.annotation.WebListener;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    /**
    * 注意还要添加@ServletComponentScan注解
    */
    @WebListener
    public class MyServletContextListener implements ServletContextListener{
        private Logger logger =  LoggerFactory.getLogger(this.getClass());
    
        @Override
        public void contextDestroyed(ServletContextEvent arg0) {
            // TODO 
            logger.info("jun: contextDestroyed");
        }
    
        @Override
        public void contextInitialized(ServletContextEvent arg0) {
            // TODO 
            logger.info("jun: ServletContextInitialized");
        }
    
    }
    

    2. cookie和session

    cookie有会话级别cookie和持久级别cookie。setDomain(String domain);setPath(String path);setMaxAge(int maxAge);
    Tomcat中的Session机制

    2.1 什么是Session

    对Tomcat而言,Session是一块在服务器开辟的内存空间,其存储结构为ConcurrentHashMap;
    session创建:服务器第一次调用getSession()时创建。
    销毁有三种情况:session过期,默认30分钟;非正常关闭服务器,正常关闭会序列化到硬盘;手动调用session.invalidate()。
    作用范围:多次请求,一次会话。

    2.2 Session的目的

    Http协议是一种无状态协议,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录;
    Session的主要目的就是为了弥补Http的无状态特性。简单的说,就是服务器可以利用session存储客户端在同一个会话期间的一些操作记录;

    2.3 实现机制

    先看两个问题,如下:
    1、服务器如何判断客户端发送过来的请求是属于同一个会话?
    答:用Session id区分,Session id相同的即认为是同一个会话,在Tomcat中Session id用JSESSIONID表示;

    2、服务器、客户端如何获取Session id?Session id在其之间是如何传输的呢? 答:服务器第一次接收到请求时,开辟了一块Session空间(创建了Session对象),同时生成一个Session id,并通过响应头的Set-Cookie:“JSESSIONID=XXXXXXX”命令,向客户端发送要求设置cookie的响应;
    客户端收到响应后,在本机客户端设置了一个JSESSIONID=XXXXXXX的cookie信息,该cookie的过期时间为浏览器会话结束;
    接下来客户端每次向同一个网站发送请求时,请求头都会带上该cookie信息(包含Session id);
    然后,服务器通过读取请求头中的Cookie信息,获取名称为JSESSIONID的值,得到此次请求的Session id;
    ps:服务器只会在客户端第一次请求响应的时候,在响应头上添加Set-Cookie:“JSESSIONID=XXXXXXX”信息,接下来在同一个会话的第二第三次响应头里,是不会添加Set-Cookie:“JSESSIONID=XXXXXXX”信息的;
    而客户端是会在每次请求头的cookie中带上JSESSIONID信息;

    举个例子:
    以chrome浏览器为例,访问一个基于tomcat服务器的网站的时候,
    浏览器第一次访问服务器,服务器会在响应头添加Set-Cookie:“JSESSIONID=XXXXXXX”信息,要求客户端设置cookie,如下图:
    在这里插入图片描述
    同时我们也可以在浏览器中找到其存储的sessionid信息,如下图

    在这里插入图片描述
    接下来,浏览器第二次、第三次…访问服务器,观察其请求头的cookie信息,可以看到JSESSIONID信息存储在cookie里,发送给服务器;且响应头里没有Set-Cookie信息,如下图:

    在这里插入图片描述
    只要浏览器未关闭,在访问同一个站点的时候,其请求头Cookie中的JSESSIONID都是同一个值,被服务器认为是同一个会话。
    再举个简单的例子加深印象,新建个Web工程,并写一个Servlet,在doGet中添加如下代码,主要做如下工作
    首先,从session中获取key为count的值,累加,存入session,并打印;
    然后,每次从请求中获取打印cookie信息,从响应中获取打印Header的Set-Cookie信息:

     /**
         * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
         */
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
            if(request.getSession().getAttribute("count") == null){
                request.getSession().setAttribute("count", 0);
                response.getWriter().write(0+"");
            }else{
                int a = Integer.parseInt(request.getSession().getAttribute("count").toString());
                request.getSession().setAttribute("count", ++a);
                response.getWriter().write(a+"");
            }
    
            Cookie[] cookies = request.getCookies();
            StringBuffer sb = new StringBuffer();
            if(cookies!=null){
                for(Cookie cookie : cookies){
                    sb.append(cookie.getName()+":"+cookie.getValue()+",");
                }
                sb.deleteCharAt(sb.length()-1);
            }
    
            System.out.println("[第"+(++index)+"次访问]from client request, cookies:" + sb);
            System.out.println("[第"+(index)+"次访问]from server response, header-Set-Cookie:" +
             response.getHeader("Set-Cookie"));;
        }
    

    部署到tomcat后,连续访问该servlet,观察控制台输出,如下,客户端第一次访问服务器的时候,在服务端的响应头里添加了JSESSIONID信息,且接下来客户端的每次访问都会带上该JSESSIONID:

    其实这里有一个问题,session劫持
    只要用户知道JSESSIONID,该用户就可以获取到JSESSIONID对应的session内容,还是以上面这个例子为例,
    我先用IE浏览器访问该站点,比如连续访问了5次,此时,session中的count值为:
    在这里插入图片描述

    查看该会话的Session id,为6A541281A79B24BC290ED3270CF15E32
    在这里插入图片描述
    接下来打开chrome控制台,将IE浏览器获取过来的JSESSIONID信息(“6A541281A79B24BC290ED3270CF15E32”)写入到cookie中,如下

    在这里插入图片描述
    接着删除其中的一个,只留下JSESSIONID为“6A541281A79B24BC290ED3270CF15E32”的cookie;
    在这里插入图片描述
    刷新页面,发现我们从session获取的count值已经变成6了,说明此次chrome浏览器的请求劫持了IE浏览器会话中的session,
    在这里插入图片描述

    2.4 Tomcat中的session实现

    Tomcat中一个会话对应一个session,其实现类是StandardSession,查看源码,可以找到一个attributes成员属性,即存储session的数据结构,为ConcurrentHashMap,支持高并发的HashMap实现;

    
        /**
         * The collection of user data attributes associated with this Session.
         */
        protected Map<String, Object> attributes = new ConcurrentHashMap<String, Object>();
    

    那么,tomcat中多个会话对应的session是由谁来维护的呢?ManagerBase类,查看其代码,可以发现其有一个sessions成员属性,存储着各个会话的session信息:

       /**
         * The set of currently active Sessions for this Manager, keyed by
         * session identifier.
         */
        protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
    

    接下来,看一下几个重要的方法,

    服务器查找Session对象的方法
    客户端每次的请求,tomcat都会在HashMap中查找对应的key为JSESSIONID的Session对象是否存在,可以查看Request的doGetSession方法源码,如下源码:

    protected Session doGetSession(boolean create) {
    
            // There cannot be a session if no context has been assigned yet
            Context context = getContext();
            if (context == null) {
                return (null);
            }
    
            // Return the current session if it exists and is valid
            if ((session != null) && !session.isValid()) {
                session = null;
            }
            if (session != null) {
                return (session);
            }
    
            // Return the requested session if it exists and is valid
            Manager manager = context.getManager();
            if (manager == null) {
                return null;        // Sessions are not supported
            }
            if (requestedSessionId != null) {
                try {
                    session = manager.findSession(requestedSessionId);
                } catch (IOException e) {
                    session = null;
                }
                if ((session != null) && !session.isValid()) {
                    session = null;
                }
                if (session != null) {
                    session.access();
                    return (session);
                }
            }
    
            // Create a new session if requested and the response is not committed
            if (!create) {
                return (null);
            }
            if ((context != null) && (response != null) &&
                context.getServletContext().getEffectiveSessionTrackingModes().
                        contains(SessionTrackingMode.COOKIE) &&
                response.getResponse().isCommitted()) {
                throw new IllegalStateException
                  (sm.getString("coyoteRequest.sessionCreateCommitted"));
            }
    
            // Re-use session IDs provided by the client in very limited
            // circumstances.
            String sessionId = getRequestedSessionId();
            if (requestedSessionSSL) {
                // If the session ID has been obtained from the SSL handshake then
                // use it.
            } else if (("/".equals(context.getSessionCookiePath())
                    && isRequestedSessionIdFromCookie())) {
                /* This is the common(ish) use case: using the same session ID with
                 * multiple web applications on the same host. Typically this is
                 * used by Portlet implementations. It only works if sessions are
                 * tracked via cookies. The cookie must have a path of "/" else it
                 * won't be provided to for requests to all web applications.
                 *
                 * Any session ID provided by the client should be for a session
                 * that already exists somewhere on the host. Check if the context
                 * is configured for this to be confirmed.
                 */
                if (context.getValidateClientProvidedNewSessionId()) {
                    boolean found = false;
                    for (Container container : getHost().findChildren()) {
                        Manager m = ((Context) container).getManager();
                        if (m != null) {
                            try {
                                if (m.findSession(sessionId) != null) {
                                    found = true;
                                    break;
                                }
                            } catch (IOException e) {
                                // Ignore. Problems with this manager will be
                                // handled elsewhere.
                            }
                        }
                    }
                    if (!found) {
                        sessionId = null;
                    }
                    sessionId = getRequestedSessionId();
                }
            } else {
                sessionId = null;
            }
            session = manager.createSession(sessionId);
    
            // Creating a new session cookie based on that session
            if ((session != null) && (getContext() != null)
                   && getContext().getServletContext().
                           getEffectiveSessionTrackingModes().contains(
                                   SessionTrackingMode.COOKIE)) {
                Cookie cookie =
                    ApplicationSessionCookieConfig.createSessionCookie(
                            context, session.getIdInternal(), isSecure());
    
                response.addSessionCookieInternal(cookie);
            }
    
            if (session == null) {
                return null;
            }
    
            session.access();
            return session;
        }
    

    先看doGetSession方法中的如下代码,这个一般是第一次访问的情况,即创建session对象,session的创建是调用了ManagerBase的createSession方法来实现的; 另外,注意response.addSessionCookieInternal方法,该方法的功能就是上面提到的往响应头写入“Set-Cookie”信息;最后,还要调用session.access方法记录下该session的最后访问时间,因为session是可以设置过期时间的;

         session = manager.createSession(sessionId);
    
            // Creating a new session cookie based on that session
            if ((session != null) && (getContext() != null)
                   && getContext().getServletContext().
                           getEffectiveSessionTrackingModes().contains(
                                   SessionTrackingMode.COOKIE)) {
                Cookie cookie =
                    ApplicationSessionCookieConfig.createSessionCookie(
                            context, session.getIdInternal(), isSecure());
    
                response.addSessionCookieInternal(cookie);
            }
    
            if (session == null) {
                return null;
            }
    
            session.access();
            return session;
    

    再看doGetSession方法中的如下代码,这个一般是第二次以后访问的情况,通过ManagerBase的findSession方法查找session,其实就是利用map的key从ConcurrentHashMap中拿取对应的value,这里的key即requestedSessionId,也即JSESSIONID,同时还要调用session.access方法,记录下该session的最后访问时间;

    
            if (requestedSessionId != null) {
                try {
                    session = manager.findSession(requestedSessionId);
                } catch (IOException e) {
                    session = null;
                }
                if ((session != null) && !session.isValid()) {
                    session = null;
                }
                if (session != null) {
                    session.access();
                    return (session);
                }
            }
    

    在session对象中查找和设置key-value的方法
    这个我们一般调用getAttribute/setAttribute方法:
    getAttribute方法很简单,就是根据key从map中获取value;
    setAttribute方法稍微复杂点,除了设置key-value外,如果添加了一些事件监听(HttpSessionAttributeListener)的话,还要通知执行,如beforeSessionAttributeReplaced, afterSessionAttributeReplaced, beforeSessionAttributeAdded、 afterSessionAttributeAdded。。。

    2.5 session存在的问题

    安全性,session劫持,这个前面已经举过例子了;
    增加服务器压力,因为session是直接存储在服务器的内存中的;
    如果存在多台服务器的话,还存在session同步问题,当然如果只有一台tomcat服务器的话,也就没有session同步的事情了,然而现在一般的应用都会用到多台tomcat服务器,通过负载均衡,同一个会话有可能会被分配到不同的tomcat服务器,因此很可能出现session不一致问题;解决session同步问题,实际上主要是保证能够抽离出一块共享空间存放session信息,且这块空间不同的tomcat服务器都可以访问到;一般这块共享的空间可以是数据库(如大多数使用到redis),或者某台服务器的内存空间,甚至硬盘空间,或者客户端的cookie也是可以的。

  • 相关阅读:
    java中的迭代器的含义
    angular-指令总结
    angular-ng-model
    angular-创建自定义的指令
    angular-$scope和$rootScope
    angular-过滤器
    Git-pull进入vim窗口解决办法
    Math常用方法
    全局变量和window属性上定义的变量
    透明点点的轮播图
  • 原文地址:https://www.cnblogs.com/seasail/p/12179392.html
Copyright © 2020-2023  润新知