HTTP本身是无状态的, 如果想要保持用户的状态, 即让一批请求都知道其属于某一个对话, 很显然就需要附带额外的信息.
Session是保持客户端信息的一种技术方式, 对于普通的浏览器, 容器会尝试将Session信息放入Cookie中, 对于不支持Cookie的浏览器, 则采用了URL重写的方式, 让客户每次访问的时候都附带上这段信息.
Session的实现和解析方式是交给每个具体容器实现的, 对于我们来说, 就是要通过获取容器提供的Session对象, 来对请求属于某一个会话进行识别, 进而在服务端可以不断的更新用户的累计状态来处理业务. 通过Session对象上附带的属性, 就可以跨越多个请求来共同完成一个业务.
从Session的发展历史也可以看到技术的变迁, 在前后端分离之后, 为了保持用户状态, 依然要用到本地存储这些额外信息. 如果客户端真的什么信息也不保存, 每次也不带任何额外信息访问服务器, 那就真的无法实现会话了.
- Session流程
- HttpSession
- 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接口.来看一些关键方法:
long getCreationTime();
, 返回创建这个session对象的时间
String getId();
, 返回sessionid
long getLastAccessedTime();
, 返回上一次访问该session的时间
void setMaxInactiveInterval(int var1)
, 设置最长的有效时间, 超过就过期了. 如果设置为-1, 则永不失效, 最好不要设置成-1.
int getMaxInactiveInterval();
, 获取有效时间, 是int形式表示的秒数.
与设置属性相关的全套方法
, 需要在会话中共享的数据, 需要放到session对象上获取.
void invalidate();
, 强制让当前session失效, 下次请求进来的时候, 容器会创建新的session对象.
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方法, 看一些比较重要的.
public Cookie(String name, String value)
, 两参数构造器, 分别传入键和值, 都是字符串, 没有无参构造器
void setMaxAge(int expiry)
, 关键方法, 设置存活时间, 如果设置为0, 关闭浏览器就失效. 默认值就是-1, 表示永远不失效.
int getMaxAge()
, 和上边是一对方法.
name和value属性的get和set方法
, 用于设置键和值.