Web基础知识(6)- Java Servlet (六) | Servlet Cookie、Servlet Session


HTTP(超文本传输协议)是一个基于请求与响应模式的无状态协议。

无状态主要指 2 点:

    (1) 协议对于事务处理没有记忆能力,服务器不能自动维护用户的上下文信息,无法保存用户状态;
    (2) 每次请求都是独立的,不会受到前面请求的影响,也不会影响后面的请求。

当浏览器发送 HTTP 请求到服务器时,服务器会响应客户端的请求,但当同一个浏览器再次发送请求到该服务器时,服务器并不知道它就是刚才那个浏览器,即 HTTP 协议的请求无法保存用户状态。

通常情况下,用户通过浏览器访问 Web 应用时,服务器都需要保存和跟踪用户的状态。例如,用户在某购物网站结算商品时,Web 服务器必须根据请求用户的身份,找到该用户所购买的商品。由于 HTTP 协议是无协议的,无法保存和跟踪用户状态,所以需要其他的方案来解决问此题,它就是会话技术。

从打开浏览器访问某个网站,到关闭浏览器的过程,称为一次会话。会话技术是指在会话中,帮助服务器记录用户状态和数据的技术。

常用的会话技术分为两种:

    (1) Cookie:客户端会话技术
    (2) Session:服务端会话技术


1. Servlet Cookie (客户端会话技术)

    Cookie 属于客户端会话技术,它是服务器发送给浏览器的小段文本信息,存储在客户端浏览器的内存中或硬盘上。当浏览器保存了 Cookie 后,每次访问服务器,都会在 HTTP 请求头中将这个 Cookie 回传给服务器。

    1) Cookie 的分类

        (1) 会话级别 Cookie(默认):Cookie 保存到浏览器的内存中,浏览器关闭则 Cookie 失效。
        (2) 持久的 Cookie:Cookie 以文本文件的形式保存到硬盘上。

    2) Cookie 的工作流程

        (1) 客户端浏览器访问服务器时,服务器通过在 HTTP 响应中增加 Set-Cookie 字段,将数据信息发送给浏览器。
        (2) 浏览器将 Cookie 保存在内存中或硬盘上。
        (3) 再次请求该服务器时,浏览器通过在 HTTP 请求消息中增加 Cookie 请求头字段,将 Cookie 回传给 Web 服务器。服务器根据 Cookie 信息跟踪客户端的状态。

    3) Cookie API

        javax.servlet.http 包中定义了一个 Cookie 类,利用它的带参构造方法,可以创建 Cookie 对象。例如:

            Cookie c = new Cookie("url", "www.test.com");

        其中参数 name 为 Cookie 的名称,参数 value 为 Cookie 的值,name 与 value 的取值不能包含 [ ] ( ) = , " / ? @ : ; 等字符

        HttpServletResponse 接口和 HttpServletRequest 接口也都定义了与 Cookie 相关的方法,如下表所示。

方法 描述
void addCookie(Cookie cookie) 用于在响应头中增加一个相应的 Set-Cookie 头字段,所属接口 javax.servlet.http.HttpServletResponse
Cookie[] getCookies() 用于获取客户端提交的 Cookie,所属接口 javax.servlet.http.HttpServletRequest


        javax.servlet.http.Cookie 类中提供了一系列获取或者设置 Cookie 的方法,如下表。

方法 描述
int getMaxAge() 用于获取指定 Cookie 的最大有效时间,以秒为单位。默认情况下取值为 -1,表示该 Cookie 保留到浏览器关闭为止。
String getName() 用于获取 Cookie 的名称。
String getPath() 用于获取 Cookie 的有效路径。
boolean getSecure() 如果浏览器只通过安全协议发送 Cookie,则返回 true;如果浏览器可以使用任何协议发送 Cookie,则返回 false。
String getValue() 用于获取 Cookie 的值。
int getVersion() 用于获取 Cookie 遵守的协议版本。
void setMaxAge(int expiry) 用于设置 Cookie 的最大有效时间,以秒为单位。取值为正值时,表示 Cookie 在经过指定时间后过期。取值为负值时,表示 Cookie 不会被持久存储,在 Web 浏览器退出时删除。取值为 0 时,表示删除该 Cookie。
void setPath(String uri) 用于指定 Cookie 的路径。
void setSecure(boolean flag) 用于设置浏览器是否只能使用安全协议(如 HTTPS 或 SSL)发送 Cookie。
void setValue(String newValue) 用于设置 Cookie 的值。


    4) Cookie 的使用细节

        使用 Cookie 开发时需要注意以下细节:

            (1) 一个 Cookie 只能标识一种信息,它至少包含一个名称(NAME)和一个值(VALUE)。
            (2) 如果创建了一个 Cookie,并发送到浏览器,默认情况下它是一个会话级别的 Cookie。用户退出浏览器就被删除。如果希望将 Cookie 存到磁盘上,则需要调用 setMaxAge(int maxAge) 方法设置最大有效时间,以秒为单位。
            (3) 使用 setMaxAge(0) 手动删除 Cookie时,需要使用 setPath 方法指定 Cookie 的路径,且该路径必须与创建 Cookie 时的路径保持一致。

    5) Cookie 的缺点

        Cookie 虽然可以解决服务器跟踪用户状态的问题,但是它具有以下缺点:

            (1) 在 HTTP 请求中,Cookie 是明文传递的,容易泄露用户信息,安全性不高。
            (2) 浏览器可以禁用 Cookie,一旦被禁用,Cookie 将无法正常工作。
            (3) Cookie 对象中只能设置文本(字符串)信息。
            (4) 客户端浏览器保存 Cookie 的数量和长度是有限制的。

    示例:

 1         // Login Page
 2         package com.example;
 3 
 4         import java.io.PrintWriter;
 5         import java.io.IOException;
 6         import java.net.URLDecoder;
 7         import java.net.URLEncoder;
 8         import java.text.SimpleDateFormat;
 9         import java.util.Date;
10 
11         import javax.servlet.http.Cookie;
12         import javax.servlet.ServletException;
13         import javax.servlet.http.HttpServlet;
14         import javax.servlet.http.HttpServletRequest;
15         import javax.servlet.http.HttpServletResponse;
16         import javax.servlet.annotation.WebServlet;
17 
18         @WebServlet("/login")
19         public class LoginServlet extends HttpServlet  {
20             protected void doGet(HttpServletRequest request, HttpServletResponse response)
21                     throws ServletException, IOException {
22 
23                 response.setContentType("text/html;charset=UTF-8");
24                 Cookie[] cookies = request.getCookies();
25                 Cookie cookie = getCookieByName(cookies, "lastTime");
26 
27                 PrintWriter writer = response.getWriter();
28                 writer.write("<h1>Login Servlet Page</h1>");
29 
30                 if (cookie == null) {
31                     writer.write("<p>Your status is Offline</p>");
32                 } else {
33                     String value = cookie.getValue();
34                     writer.write("<p>Your status is Online, last visit time is " + URLDecoder.decode(value)
35                                     + ". Click <a href=\"/logout\" > Here </a> to logout</p>");
36                 }
37 
38                 Date date = new Date();
39                 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
40                 String sDate = sdf.format(date);
41 
42                 Cookie c = new Cookie("lastTime", URLEncoder.encode(sDate));
43                 c.setMaxAge(60 * 60 * 24);    // 秒, 设置有效时间为一天
44                 c.setPath("/login");
45                 response.addCookie(c);
46 
47             }
48 
49             protected void doPost(HttpServletRequest request, HttpServletResponse response)
50                     throws ServletException, IOException {
51             }
52 
53             public static Cookie getCookieByName(Cookie[] cookies, String name) {
54                 if (cookies == null) {
55                     return null;
56                 } else {
57                     for (Cookie cookie : cookies) {
58                         if (cookie.getName().equals(name)) {
59                             return cookie;
60                         }
61                     }
62                     return null;
63                 }
64             }
65         }
66 
67         // Logout Page
68         package com.example;
69 
70         import java.io.IOException;
71 
72         import javax.servlet.http.Cookie;
73         import javax.servlet.ServletException;
74         import javax.servlet.http.HttpServlet;
75         import javax.servlet.http.HttpServletRequest;
76         import javax.servlet.http.HttpServletResponse;
77         import javax.servlet.annotation.WebServlet;
78 
79 
80         @WebServlet("/logout")
81         public class LogoutServlet extends HttpServlet  {
82 
83             protected void doGet(HttpServletRequest request, HttpServletResponse response)
84                                 throws ServletException, IOException {
85                 Cookie cookie = new Cookie("lastTime", "");
86                 cookie.setMaxAge(0);    // 设置有效时间为0,删除cookie
87                 cookie.setPath("/login");
88                 response.addCookie(cookie);
89                 response.sendRedirect("/login");
90             }
91 
92             protected void doPost(HttpServletRequest request, HttpServletResponse response)
93                                 throws ServletException, IOException {
94             }
95 
96         }


2. Servlet Session (服务端会话技术)

    Session 是服务器端会话技术。当浏览器访问 Web 服务器的资源时,服务器可以为每个用户浏览器创建一个 Session 对象,每个浏览器独占一个 Session 对象。

    由于每个浏览器独占一个 Session,所以用户在访问服务器的资源时,可以把数据保存在各自的 Session 中。当用户再次访问该服务器中的其它资源时,其它资源可以从 Session 中取出数据,为用户服务。

    1) Session 的工作原理

        Session 虽然属于服务端会话技术,但是它的实现离不开客户端浏览器和 Cookie 的支持,其工作原理如下。

        (1) 当客户端第一次请求会话对象时,服务器会创建一个 Session 对象,并为该 Session 对象分配一个唯一的 SessionID(用来标识这个 Session 对象);
        (2) 服务器将 SessionID 以 Cookie(Cookie 名称为:“JSESSIONID”,值为 SessionID 的值)的形式发送给客户端浏览器;
        (3) 客户端浏览器再次发送 HTTP 请求时,会将携带 SessionID 的 Cookie 随请求一起发送给服务器;
        (4) 服务器从请求中读取 SessionID,然后根据 SessionID 找到对应的 Session 对象。

        注意:

            (1) 流程中的 Cookie 是容器自动生成的,它的 maxAge 属性取值为 -1,表示仅当前浏览器有效。
            (2) 浏览器关闭时,对应的 Session 并没有失效,但此时与此 Session 对应的 Cookie 已失效,导致浏览器无法再通过 Cookie 获取服务器端的 Session 对象。
            (3)同一浏览器的不同窗口共享同一 Session 对象,但不同浏览器窗口之间不能共享 Session 对象。

    2) Session 与 Cookie 对比

        Session 和 Cookie 都属于会话技术,都能帮助服务器保存和跟踪用户状态,但两者也存在差异,如下表。

不同点 Cookie Session
存储位置不同 Cookie 将数据存放在客户端浏览器内存中或硬盘上。 Session 将数据存储在服务器端。
大小和数量限制不同 浏览器对 Cookie 的大小和数量有限制。 Session 的大小和数量一般不受限制。
存放数据类型不同 Cookie 中保存的是字符串。 Session 中保存的是对象。
安全性不同 Cookie 明文传递,安全性低,他人可以分析存放在本地的 Cookie 并进行 Cookie 欺骗。 Session 存在服务器端,安全性较高。
对服务器造成的压力不同 Cookie 保存在客户端,不占用服务器资源。 Session 保存在服务端,每一个用户独占一个 Session。若并发访问的用户十分多,就会占用大量服务端资源。
跨域支持上不同 Cookie 支持跨域名访问。 Session 不支持跨域名访问。

 
    3) Session API

        Session 对象由服务器创建,通过 HttpServletRequest.getSession() 方法可以获得 HttpSession 对象,例如:

            HttpSession session=request.getSession();

        HttpSession 接口定义了一系列对 Session 对象操作的方法,如下表。

方法 描述
long getCreationTime() 返回创建 Session 的时间。
String getId() 返回获取 Seesion 的唯一的 ID。
long getLastAccessedTime()  返回客户端上一次发送与此 Session 关联的请求的时间。
int getMaxInactiveInterval()  返回在无任何操作的情况下,Session 失效的时间,以秒为单位。
ServletContext getServletContext() 返回 Session 所属的 ServletContext 对象。
void invalidate() 使 Session 失效。
void setMaxInactiveInterval(int interval) 指定在无任何操作的情况下,Session 失效的时间,以秒为单位。负数表示 Session 永远不会失效。


    4) 设置 Session 过期时间

        Session 对象在服务器中驻留一段时间后没有被使用,就会被销毁,这个时间就是 Session 的过期时间。

        Session 的默认过期时间为 30 分钟,我们可以通过如下两种方式设置过期时间。

        (1) 使用 <session-config> 元素

            在 web.xml 中,使用 <session-config> 及其子元素 <session-timeout> 可以配置 Session 的默认过期时间,代码如下。

                <web-app>

                    <!--设置 session 的过期时间-->
                    <session-config>
                        <session-timeout>10</session-timeout>
                    </session-config>

                </web-app>

            其中:

                <session-timeout> 元素用来指定默认 Session 过期时间,以分钟为单位,该元素值必须为整数。
                <session-timeout> 元素的值为零或负数,表示 Session 永远不会过期。

        (2) 调用 setMaxInactiveInterval() 方法

            通过调用 session.setMaxInactiveInterval(int interval) 设置过期时间,以秒为单位,零和负数表示会话永远不会过期,代码如下。

                request.getSession().setMaxInactiveInterval(100);      

     5) Session 的生命周期

        (1) Session 对象创建

            Session 对象在容器第一次调用 request.getSession() 方法时创建。 

           值得注意的是,当客户端访问的 Web 资源是 HTML,CSS,图片等静态资源时,服务器不会创建 Session 对象。

        (2) Session 对象销毁

            Session 对象在如下 3 种情况下会被销毁:

                a) Session 过期;
                b) 调用 session.invalidate() 方法,手动销毁 Session;
                c) 服务器关闭或者应用被卸载。

    6) Session 域对象

        Session 对象也是一种域对象,它可以对属性进行操作,进而实现会话中请求之间的数据通讯和数据共享。

        在 javax.servlet.http.HttpSession 接口中定义了一系列操作属性的方法,如下表。

方法 描述
void setAttribute(String name, Object o) 把一个 Java 对象与一个属性名绑定,并将它作为一个属性存放到 Session 对象中。参数 name 为属性名,参数 object 为属性值。
Object getAttribute(String name)  根据指定的属性名 name,返回 Session 对象中对应的属性值。
void removeAttribute(String name) 从 Session 对象中移除属性名为 name 的属性。
Enumeration getAttributeNames() 用于返回 Session 对象中的所有属性名的枚举集合。


        Session 、request 以及 ServletContext 合称为 Servlet 的三大域对象,它们都能保存和传递数据,但是三者也存在许多差异,如下表。    

不同 request Session ServletContext
创建 客户端向容器发送请求时创建。 容器第一次调用 getSession() 方法时创建。 Servlet 容器启动时创建。
销毁 容器对这次请求做出响应后销毁。

关闭服务器或应用被卸载。

Session 过期,默认为 30 分钟。

手动调用 session.invalidate() 方法进行销毁。

容器关闭或者 Web 应用被移除时销毁。
有效范围 只对当前请求涉及的 Servlet 有效。 Session 对本次会话期间的所有 Servlet 都有效。 对整个 Web 应用内的所有 Servlet 有效。
数量 Servlet 实例可以有多个 request 对象。 Web 应用中可以有多个 Session,多个 Servet 实例可以共享同一 Session 对象。 在整个 Web 应用中只有一个 Context 对象。
数据共享 通过和请求转发的配合使用可以实现一次请求中 Web 组件之间共享的数据。 通过 Session 域对象可以实现一次会话中的多个请求之间共享数据。 通过 Context 对象,可以实现多次会话之间的数据共享。


    示例:

 1         // Login Session Page
 2         package com.example;
 3 
 4         import javax.servlet.ServletException;
 5         import javax.servlet.annotation.WebServlet;
 6         import javax.servlet.http.HttpServlet;
 7         import javax.servlet.http.HttpServletRequest;
 8         import javax.servlet.http.HttpServletResponse;
 9         import java.io.IOException;
10         import java.io.PrintWriter;
11         import java.text.SimpleDateFormat;
12         import java.util.Date;
13 
14         @WebServlet("/loginSession")
15         public class SessionLogin extends HttpServlet {
16 
17             protected void doGet(HttpServletRequest request, HttpServletResponse response)
18                                     throws ServletException, IOException {
19 
20                 response.setContentType("text/html;charset=UTF-8");
21                 PrintWriter writer = response.getWriter();
22                 writer.write("<h1>Login Session Page</h1>");
23 
24                 Date date = new Date();
25                 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
26                 Date CreationTime = new Date(request.getSession().getCreationTime());
27 
28                 // 会话上次关联的时间
29                 Date LastAccessedTime = new Date(request.getSession().getLastAccessedTime());
30                 String sDate = sdf.format(date);
31 
32                 // 将当前时间赋值到session域对象中
33                 request.getSession().setAttribute("lastTime", sDate);
34                 // 设置会话的失效时间,以秒为单位
35                 request.getSession().setMaxInactiveInterval(60);
36 
37                 // 对session中各个信息输出到页面
38                 writer.write("<p>Current Time: " + sDate  + "<br/>"
39                         + "Current SessionID: " + request.getSession().getId() + "<br/>"
40                         + "Creation Time: " + sdf.format(CreationTime) + "<br/>"
41                         + "Last Accessed Time: " + sdf.format(LastAccessedTime) + "<br/>"
42                         + "Max Inactive Interval(Seconds): " + request.getSession().getMaxInactiveInterval() + "</p>"
43                 );
44 
45                 String url = response.encodeURL("/loginSessionView");
46                 writer.write("<a href=" + url + ">Try again</a>");
47             }
48 
49             protected void doPost(HttpServletRequest request, HttpServletResponse response)
50                                     throws ServletException, IOException {
51             }
52         }
53 
54         // Session View Page
55         package com.example;
56 
57         import javax.servlet.ServletException;
58         import javax.servlet.annotation.WebServlet;
59         import javax.servlet.http.HttpServlet;
60         import javax.servlet.http.HttpServletRequest;
61         import javax.servlet.http.HttpServletResponse;
62         import java.io.IOException;
63         import java.io.PrintWriter;
64         import java.text.SimpleDateFormat;
65         import java.util.Date;
66 
67         @WebServlet("/loginSessionView")
68         public class SessionView extends HttpServlet {
69 
70             protected void doGet(HttpServletRequest request, HttpServletResponse response)
71                                 throws ServletException, IOException {
72 
73                 response.setContentType("text/html;charset=UTF-8");
74                 PrintWriter writer = response.getWriter();
75 
76                 writer.write("<h1>Login Session View Page</h1>");
77 
78                 // 从 session 中获取上次访问的时间
79                 String value = (String) request.getSession().getAttribute("lastTime");
80 
81                 if (value == null) {
82                     writer.write("<p>Welcome to Login Session View Page</p>");
83                 } else {
84                     writer.write("<p>Welcome to Login Session View Page again, your last visit time is " + value + "</p>");
85                 }
86 
87                 Date date = new Date();
88                 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
89                 String sDate = sdf.format(date);
90                 request.getSession().setAttribute("lastTime", sDate);
91             }
92 
93             protected void doPost(HttpServletRequest request, HttpServletResponse response)
94                                     throws ServletException, IOException {
95             }
96         }



posted @ 2022-03-16 14:38  垄山小站  阅读(130)  评论(0)    收藏  举报