Java Web Reinforcement 04 Session

Java Web Reinforcement 04 Session

HTTP本身是无状态的, 如果想要保持用户的状态, 即让一批请求都知道其属于某一个对话, 很显然就需要附带额外的信息. Session是保持客户端信息的一种技术方式, 对于普通的浏览器, 容器会尝试将Session信息放入Cookie中, 对于不支持Cookie的浏览器, 则采用了URL重写的方式,

HTTP本身是无状态的, 如果想要保持用户的状态, 即让一批请求都知道其属于某一个对话, 很显然就需要附带额外的信息. Session是保持客户端信息的一种技术方式, 对于普通的浏览器, 容器会尝试将Session信息放入Cookie中, 对于不支持Cookie的浏览器, 则采用了URL重写的方式, 让客户每次访问的时候都附带上这段信息. Session的实现和解析方式是交给每个具体容器实现的, 对于我们来说, 就是要通过获取容器提供的Session对象, 来对请求属于某一个会话进行识别, 进而在服务端可以不断的更新用户的累计状态来处理业务. 通过Session对象上附带的属性, 就可以跨越多个请求来共同完成一个业务. 从Session的发展历史也可以看到技术的变迁, 在前后端分离之后, 为了保持用户状态, 依然要用到本地存储这些额外信息. 如果客户端真的什么信息也不保存, 每次也不带任何额外信息访问服务器, 那就真的无法实现会话了.
  1. Session流程
  2. HttpSession
  3. Cookie

Session流程简述

对于如何标识一个会话, 肯定没有比为其设置一个唯一的ID更加方便了. 对于客户的第一个请求, 容器都会自动生成一个SessionID, 并且尝试通过响应发送给客户端, 客户端按照默认的策略, 每次会附带与这个网站相关的Cookie进行访问, cookie其中存放了sessionid, 这样服务器就认出了用户的session, 在多次请求中便可以访问同一个session对象了. 先来看最常见的方式, 就是通过Cookie传递ID. Cookie本身就是一个键值对构成的字符串, 放在头部信息中. 会由浏览器自动接收和存储. 服务端的响应头部是 Set-Cookie, 客户端的请求的头部是 Cookie. 容器会在把HTTP请求交给我们的servlet之前, 就把cookie中的session信息处理好, 从其中获取sessionid, 并寻找现有的sessionid进行匹配, 还会将session对象的引用包装在当前的请求对象中. 需要在响应中发一个会话cookie的话, 只需要执行 HttpSession session = request.getSession(), (实际上现代的Web容器,不进行设置也会发sessionid), 之后的设置工作容器会完全自己完成. 获取session对象也是这句话, 容器后台会进行大量的工作, 包括判断是否含有sessionid, 没有就新建, 有就匹配已经存在的session对象等一系列工作, 最终返回一个标识当前请求所属的会话对象. session.isNew()可以用来判断此次请求的session是新创建的, 还是已经存在. 这里还涉及到使用Cookie对象, 其实用法很简单, 只不过cookie和session一样都有特殊的控制方法.稍后再介绍. 对于禁止了Cookie的浏览器来说, 就要想另外一种办法附加额外的信息, 这种办法就是将响应返回的时候, 在返回的所有链接中让URL请求参数最后附带上sessionid, 容器会对之后的请求进行解析, 同样可以使用会话. 要使用这种功能, 就需要显式的使用response对象的encodeURL和encodeRedirectURL方法. 先看前者.
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.setCharacterEncoding("UTF-8");

    resp.setContentType("text/html");

    PrintWriter printWriter = resp.getWriter();

    //禁止Cookie的情况下必须写这一行, 否则无法重写URL
    HttpSession session = req.getSession();

    printWriter.println("<a href=\"" + resp.encodeURL("/attr") + "\">带有sessionid的重写URL链接</a>");

}
注意, 如果浏览器启用了cookie, 则看不到重写后的URL, 禁止cookie之后, 每次访问这个方法, 都会生成一个新的带有sessionid的链接, 这就说明服务器端每次接受到的请求都是不含cookie的, 只好新生成一个session然后重写URL, 如果你继续点击, 就可以延续会话. 如果不点击, 就完蛋了. 可见这种方法很麻烦, 如果用户转而点击其他, 就必须再生成session对象, 所以还是默认使用cookie比较方便. 如果想重定向怎么办, 这个时候因为301响应可以控制用户浏览器访问的URL, 所以使用encodeRedirectURL可以获取重写后的URL, 再发送重定向响应就可以了:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //禁止Cookie的情况下必须写这一行, 否则无法重写URL
    HttpSession session = req.getSession();

    String s = resp.encodeRedirectURL("/attr");
    resp.sendRedirect(s);
}

HttpSession对象

知道了session的整体流程, 就可以来详细看看HttpSession对象, 也就是作为应用程序员的我们可以如何控制容器中的session. HttpSession也是一个接口, 具体实现类也就是获取的session是由容器生成的, 这是一个独立的接口, 并没有更上层的Session接口.来看一些关键方法:
  1. long getCreationTime();, 返回创建这个session对象的时间
  2. String getId();, 返回sessionid
  3. long getLastAccessedTime();, 返回上一次访问该session的时间
  4. void setMaxInactiveInterval(int var1), 设置最长的有效时间, 超过就过期了. 如果设置为-1, 则永不失效, 最好不要设置成-1.
  5. int getMaxInactiveInterval();, 获取有效时间, 是int形式表示的秒数.
  6. 与设置属性相关的全套方法, 需要在会话中共享的数据, 需要放到session对象上获取.
  7. void invalidate();, 强制让当前session失效, 下次请求进来的时候, 容器会创建新的session对象.
  8. boolean isNew();, 是否是新建的session.
Session的方法比较少一些, 除了设置属性之外, 常用的就是与session过期管理相关的获取时间或者设置间隔的属性, 有了这些东西, 就可以任意的设置session是根据时间间隔还是具体时间到期. 不过上边的获取session对象再设置, 设置的仅仅是当前会话对象. 如果想修改全局设置, 可以在web.xml中统一配置过期时间:
<?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">

    ......

    <session-config>
        <session-timeout>15</session-timeout>
    </session-config>

</web-app>
总的来说, 只要客户端支持Cookie, 对session的管理就方便很多, 可以在获取session对象之后进行各种操作. 将用户的身份识别信息(比如登录与否)放在session中是常见做法.

Cookie

Cookie对象本质是一段字符串, 用于服务器需要让客户端保存的信息, 浏览器在每次访问某个网站的时候, 会自动附带该网站对应的Cookie. Cookie有一些设置是为了让浏览器知道如何使用, 比如Cookie的过期时间, 超过该时间后, 浏览器便不会再使用该cookie. 默认情况下, 只要用户关闭浏览器, 会话和cookie都会失效. 但是可以设置时间, 让cookie更长久, 许多网站的自动登录技术都是通过cookie(或者更现代的本地存储)来完成的. 无论如何, 当你使用一台全新的计算机,全新的浏览器, 没有保存任何与该网站相关的历史数据的时候, 肯定就不能实现自动登录了. 所以cookie不仅可以用于会话, 也可以用于其他你想让客户端暂时存储的信息. cookie是放在请求和响应的头部信息中的, 但不要直接操作, 操作cookie通过javax.servlet.http.cookie对象来操作. 从请求中可以获取cookie, 而响应对象可以新增cookie. 先来看一下简单使用:
@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        resp.setCharacterEncoding("UTF-8");

        resp.setContentType("text/html");

        PrintWriter out = resp.getWriter();

        //获取cookie并且打印内容
        Arrays.stream(req.getCookies()).forEach(cookie -> {
            out.println(cookie.getName() + "<br>");
            out.println(cookie.getValue() + "<br>");
            out.println(cookie.getComment() + "<br>");
            out.println(cookie.getDomain() + "<br>");
            out.println(cookie.getMaxAge() + "<br>");
            out.println(cookie.getPath() + "<br>");
            out.println(cookie.getSecure() + "<br>");
            out.println(cookie.getVersion() + "<br>");
            out.println("<hr>");
        });

        Cookie saner = new Cookie("name", "saner");
        Cookie choco = new Cookie("name", "choco");
        Cookie owl = new Cookie("owl", "sixtuan");

        //仅用于HTTP访问
        saner.setHttpOnly(true);
        //设置有效时间, 为秒数
        saner.setMaxAge(1800);

        resp.addCookie(saner);
        resp.addCookie(choco);
        resp.addCookie(owl);
    }
用Chrome监控, 可以看到响应头中的信息如下:
Set-Cookie: name=saner; Max-Age=1800; Expires=Fri, 25-Oct-2019 06:32:08 GMT; HttpOnly
Set-Cookie: name=choco
Set-Cookie: owl=sixtuan
来看看Cookie的API. Cookie并不是一个接口, 而是一个实现了Clonable和序列化的具体类. 然后有一系列set和get方法, 看一些比较重要的.
  1. public Cookie(String name, String value), 两参数构造器, 分别传入键和值, 都是字符串, 没有无参构造器
  2. void setMaxAge(int expiry), 关键方法, 设置存活时间, 如果设置为0, 关闭浏览器就失效. 默认值就是-1, 表示永远不失效.
  3. int getMaxAge(), 和上边是一对方法.
  4. name和value属性的get和set方法, 用于设置键和值.
LICENSED UNDER CC BY-NC-SA 4.0
Comment