03-Web技术-Cookie+Session

第03章-WEB技术-Cookie+Session

学习目标

  • 掌握 Cookie 的特征和使用方法
  • 掌握 Session 的使用方法

Cookie 简介

什么是 Cookie

cookie,有时我们也用其复数形式 cookies,是服务端保存在浏览器端的数据片段。

key/value 的形式进行保存。每次请求的时候,请求头会自动包含本网站此目录下的 cookie 数据。网站经常使用这个技术来识别用户是否登陆等功能。

简单的说,cookie 就是服务端留给计算机用户浏览器端的小文件。

HTTP 是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不能区分中 两次请求是否由一个客户端发出。这样的设计严重阻碍的 Web 程序的设计。

如:在我 们进行网购时,买了一条裤子,又买了一个手机。由于 http 协议是无状态的,如果不 通过其他手段,服务器是不能知道用户到底买了什么。而 Cookie 就是解决方案之一。

Cookie 实际上就是服务器保存在浏览器上的一段信息。浏览器有了 Cookie 之后,每次向服务器发送请求时都会同时将该信息发送给服务器,服务器收到请求后,就可以根据 该信息处理请求。

例如:我们上文说的网上商城,当用户向购物车中添加一个商品时,服务器会将这个条信息封装成一个 Cookie 发送给浏览器,浏览器收到 Cookie,会将它保存在内存中(注意这里的内存是本机内存,而不是服务器内存),那之后每次向服务器发送请求,浏览器都会携带该 Cookie,而服务器就可以通过读取 Cookie 来判断用户到底买了哪些商品。

当用户进行结账操作时,服务器就可以根据 Cookie 的信息来做结算。

Cookie 的用途:

  • 网上商城的购物车
  • 保持用户登录状态

Cookie 的缺点

  • Cookie 做为请求或响应报文发送,无形中增加了网络流量。
  • Cookie 是明文传送的安全性差。
  • Cookie 中保存数据是不稳定的,用户可以随时清理 Cookie
  • 各个浏览器对 Cookie 有限制,使用上有局限

Cookie存储位置

chromecookie 位置:

C:\Users\lfy\AppData\Local\Google\Chrome\User Data\Default\Cookies

iecookie 位置:

C:\Users\lfy\AppData\Local\Microsoft\Windows\InetCache

Cookie 方法

  • public void setDomain(String pattern)
  • 该方法设置 cookie 适用的域,例如 runoob.com。
  • public String getDomain()
  • 该方法获取 cookie 适用的域,例如 runoob.com。
  • public void setMaxAge(int expiry)
  • 该方法设置 cookie 过期的时间(以秒为单位)。如果不这样设置,cookie 只会在当前 session 会话中持续有效。
  • public int getMaxAge()
  • 该方法返回 cookie 的最大生存周期(以秒为单位),默认情况下,-1 表示 cookie 将持续下去,直到浏览器关闭。
  • public String getName()
  • 该方法返回 cookie 的名称。名称在创建后不能改变。
  • public void setValue(String newValue)
  • 该方法设置与 cookie 关联的值。
  • public String getValue()
  • 该方法获取与 cookie 关联的值。
  • public void setPath(String uri)
  • 该方法设置 cookie 适用的路径。如果您不指定路径,与当前页面相同目录下的(包括子目录下的)所有 URL 都会返回 cookie。
  • public String getPath()
  • 该方法获取 cookie 适用的路径。
  • public void setSecure(boolean flag)
  • 该方法设置布尔值,表示 cookie 是否应该只在加密的(即 SSL)连接上发送。
  • public void setComment(String purpose)
  • 设置cookie的注释。该注释在浏览器向用户呈现 cookie 时非常有用。
  • public String getComment()
  • 获取 cookie 的注释,如果 cookie 没有注释则返回 null。

Cookie 原理

总的来看 Cookie 像是服务器发给浏览器的一张“会员卡”,浏览器每次向服务器发送请求时都会带着这张“会员卡”,当服务器看到这张“会员卡”时就可以识别浏览器的身份。

实际上这个所谓的“会员卡”就是服务器发送的一个响应头

img

如图 Set-Cookie 这个响应头就是服务器在向服务器发“会员卡”,这个响应头的名字 是 Set-Cookie

浏览器收到该信息后就会将它保存到内存或硬盘中。

当浏览器再次向服务器发送请求时就会携带这个 Cookie 信息:

img

Cookie 的使用

cookie 是由服务端创建的,由浏览器端保存的。所以创建对象我们应该在服务端创建 cookiecookie 的创建方法:

(1)Cookie 创建

ServletdoPost()方法中编写如下代码:

1
2
3
4
5
6
7
8
9
// 创建一个Cookie对象
Cookie cookie1 = new Cookie("username", "zhangsan");
Cookie cookie2 = new Cookie("password", "123456");
Cookie cookie3 = new Cookie("age", "20");

// 将Cookie对象放入response对象中
response.addCookie(cookie1);
response.addCookie(cookie2);
response.addCookie(cookie3);

请记住,无论是名字还是值,都不应该包含空格或以下任何字符:

[ ] ( ) = , " / ? @ : ;

如果是中文则需要编码。Servlet Cookie 处理需要对中文进行编码与解码:

1
2
String str = java.net.URLEncoder.encode("中文""UTF-8");       //编码
String str = java.net.URLDecoder.decode("编码后的字符串","UTF-8");  // 解码

(2)Cookie 访问

在浏览器中访问该 Servlet,会发现响应头中出现如下内容:

Set-Cookie: username=zhangsan; password=123456; age=20

如此就成功的向浏览器设置了一个 Cookie,当我们在刷新页面时会发现浏览器的请求头中出。现如下代码:

Cookie: username=zhangsan; password=123456; age=20

获取所有 Cookie

1
2
3
4
5
6
7
// 获取带客户端的数据
Cookie[] cookies = request.getCookies();
if(cookies != null){
    for (Cookie cookie2 : cookies) {	
        System.out.println(cookie2.getName()+"="+cookie2.getValue());
    }
}

(3)Cookie 有效时间

经过上边的介绍我们已经知道 Cookie 是存储在浏览器中的,但是可想而知一般情况下浏览器不可能永远保存一个 Cookie,一来是占用硬盘空间,再来一个 Cookie 可能只在某一时刻有用没必要长久保存。

所以我们还需要为 Cookie 设置一个有效时间。 通过 Cookie 对象的 setMaxAge()可以设置 Cookie 的有效时间。 其中 setMaxAge()接收一个 int 型的参数,来设置有效时间。

参数主要有一下四种情况:

  • 设置等于 0,setMaxAge(0)
  • Cookie 立即失效,下次浏览器发送请求将不会在携带该 Cookie
  • 设置大于 0,setMaxAge(60)
  • 表示有效的秒数 60 就代表 60 秒即 1 分钟,也就是 Cookie 在 1 分钟后失效。
  • 设置小于 0,setMaxAge(-1)
  • 设置为负数表示当前会话有效。也就是关闭浏览器后 Cookie 失效
  • 不设置
  • 如果不设置失效时间,则默认当前会话有效。

如:

1
2
3
4
5
6
7
8
9
10
11
// cookie 的有效期.默认情况下 7天
// 关闭浏览器后,cookie不存在.----->没有设置cookie的有效期
/**
	* expiry(有效) 以秒为单位
	* getMaxAge():返回以秒为单位的cookie的最大生存时间,默认情况下,-1表示该cookie将保留到浏览器关闭位置.
	* setMaxAge():以秒为单位
	* 正值表示cookie将在经过该值表示的秒数后过期.注意:该值是cookie过期的最大生存时间,不是cookie的当前生存时间.
	* 负值表示cookie不会被持久存储,将在web浏览器退出时删除.0会导致删除cookie[关闭浏览器,cookie就失效.默认值-1]
	*/
// 有效期设为7天
cookie.setMaxAge(60 * 60 * 24 * 7);

案例:读写Cookie

设置Cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.star.cookie;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/index")
public class Demo01 extends HttpServlet {
private static final long serialVersionUID = 1L;

<span class="hljs-annotation">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">service</span><span class="hljs-params">(HttpServletRequest request, HttpServletResponse response)</span>
		<span class="hljs-keyword">throws</span> ServletException, IOException </span>{
	String param1 = request.getParameter(<span class="hljs-string">"name"</span>) == <span class="hljs-keyword">null</span> ? <span class="hljs-string">"名字"</span> : request.getParameter(<span class="hljs-string">"name"</span>);
	String param2 = request.getParameter(<span class="hljs-string">"url"</span>) == <span class="hljs-keyword">null</span> ? <span class="hljs-string">"名字"</span> : request.getParameter(<span class="hljs-string">"url"</span>);
	<span class="hljs-comment">// 为名字和姓氏创建 Cookie</span>
	<span class="hljs-comment">// 中文转码为%E6%B5%8B%E8%AF%95格式</span>
	Cookie name = <span class="hljs-keyword">new</span> Cookie(<span class="hljs-string">"name"</span>, URLEncoder.encode(param1, <span class="hljs-string">"UTF-8"</span>));
	Cookie url = <span class="hljs-keyword">new</span> Cookie(<span class="hljs-string">"url"</span>, param2);

	<span class="hljs-comment">// 为两个 Cookie 设置过期日期为 24 小时后</span>
	name.setMaxAge(<span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">24</span>);
	url.setMaxAge(<span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">24</span>);

	<span class="hljs-comment">// 在响应头中添加两个 Cookie</span>
	response.addCookie(name);
	response.addCookie(url);

	<span class="hljs-comment">// 设置响应内容类型</span>
	response.setContentType(<span class="hljs-string">"text/html;charset=UTF-8"</span>);

	PrintWriter out = response.getWriter();
	String title = <span class="hljs-string">"设置 Cookie 实例"</span>;
	String docType = <span class="hljs-string">"&lt;!DOCTYPE html&gt;"</span>;
	out.println(docType + <span class="hljs-string">"&lt;html&gt;&lt;head&gt;&lt;title&gt;"</span> + title + <span class="hljs-string">"&lt;/title&gt;&lt;/head&gt;"</span>
			+ <span class="hljs-string">"&lt;body bgcolor='#f0f0f0'&gt;设置了两个cookie[name|url]&lt;/body&gt;&lt;/html&gt;"</span>);
}

}

要读取 Cookie,需要通过调用 HttpServletRequestgetCookies() 方法创建一个 javax.servlet.http.Cookie 对象的数组。

然后循环遍历数组,并使用 getName()getValue() 方法来访问每个 cookie 和关联的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.star.cookie;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLDecoder;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/index2")
public class Demo02 extends HttpServlet {
private static final long serialVersionUID = 1L;

<span class="hljs-annotation">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">service</span><span class="hljs-params">(HttpServletRequest request, HttpServletResponse response)</span>
		<span class="hljs-keyword">throws</span> ServletException, IOException </span>{
	Cookie cookie = <span class="hljs-keyword">null</span>;
	Cookie[] cookies = <span class="hljs-keyword">null</span>;
	<span class="hljs-comment">// 获取与该域相关的 Cookie 的数组</span>
	cookies = request.getCookies();

	<span class="hljs-comment">// 设置响应内容类型</span>
	response.setContentType(<span class="hljs-string">"text/html;charset=UTF-8"</span>);

	PrintWriter out = response.getWriter();

	<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; cookies.length; i++) {
		cookie = cookies[i];
		<span class="hljs-keyword">if</span> ((cookie.getName()).compareTo(<span class="hljs-string">"name"</span>) == <span class="hljs-number">0</span>) {
			out.print(<span class="hljs-string">"读取到的cookie:"</span> + cookie.getName() + <span class="hljs-string">"&lt;br/&gt;"</span>);
		}
		out.print(<span class="hljs-string">"名称:"</span> + cookie.getName() + <span class="hljs-string">","</span>);
		out.print(<span class="hljs-string">"值:"</span> + URLDecoder.decode(cookie.getValue(), <span class="hljs-string">"utf-8"</span>) + <span class="hljs-string">" &lt;br/&gt;"</span>);
	}
}

}

删除 Cookie 非常简单,只需要按照以下三个步骤进行:

  • 读取一个现有的 Cookie,并把它存储在 Cookie 对象中。
  • 使用 setMaxAge() 方法设置 Cookie 的年龄为零,来删除现有的 Cookie。
  • 把这个 Cookie 添加到响应头。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.star.cookie;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/index3")
public class Demo03 extends HttpServlet {
private static final long serialVersionUID = 1L;

<span class="hljs-annotation">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">service</span><span class="hljs-params">(HttpServletRequest request, HttpServletResponse response)</span>
		<span class="hljs-keyword">throws</span> ServletException, IOException </span>{
	Cookie cookie = <span class="hljs-keyword">null</span>;
	Cookie[] cookies = <span class="hljs-keyword">null</span>;
	<span class="hljs-comment">// 获取与该域相关的 Cookie 的数组</span>
	cookies = request.getCookies();

	<span class="hljs-comment">// 设置响应内容类型</span>
	response.setContentType(<span class="hljs-string">"text/html;charset=UTF-8"</span>);

	PrintWriter out = response.getWriter();

	<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; cookies.length; i++) {
		cookie = cookies[i];
		<span class="hljs-keyword">if</span> ((cookie.getName()).compareTo(<span class="hljs-string">"url"</span>) == <span class="hljs-number">0</span>) {
			cookie.setMaxAge(<span class="hljs-number">0</span>);
			response.addCookie(cookie);
			out.print(<span class="hljs-string">"已删除的 cookie:"</span> + cookie.getName() + <span class="hljs-string">"&lt;br/&gt;"</span>);
		}
	}
}

}

(4)Cookie 路径

Cookie 的路径指告诉浏览器访问哪些地址时该携带该 Cookie。

我们知道浏览器会保存很多不同网站的 Cookie,比如百度的 Cookie,新浪的 Cookie,腾讯的 Cookie 等等。

那我们不可能访问百度的时候携带新浪的 Cookie,也不可能访问每个网站时都带上所有 的 Cookie 这是不现实的,所以往往我们还需要为 Cookie 设置一个 Path 属性,来告诉浏 览器何时携带该 Cookie。

可以同过 Cookie 的 setPath()来设置路径,这个路径是由浏览器来解析的,所以/代表服务器的根目录。

如:

设置为 /项目名/路径

cookie.setPath("/项目名/路径") 

这样设置只有访问/项目名/路径 下的的资源才会携带 Cookie。

如果不设置,默认会在访问/项目名下的资源时携带。如:/项目名/index.jsp/项目名/hello/index.jsp

如:

1
2
3
4
5
6
Cookie cookie = new Cookie("username", "abc");
cookie.setMaxAge(60*60*24);
// 秒为单位,一天后过期
cookie.setPath(getServletContext().getContextPath() + "/");
resp.addCookie(cookie);
resp.sendRedirect(getServletContext().getContextPath() + "/index.jsp"); // 可以读取 cookie

Session

HTTP 是一种“无状态”协议,这意味着每次客户端检索网页时,客户端打开一个单独的连接到 Web 服务器,服务器会自动不保留之前客户端请求的任何记录。

Session 工作原理

session 是基于 cookie 的。 在用户第一次使用 session 的时候,服务器会为用户创建一个 session 域对象。并且使用 jsessionid 和这

个对象关联,这个对象在整个用户会话期间使用。并且返回 set-cookie:jsessionid=xxx 的项。

用户下次以后的请求都会携带 jsessionid 这个参数,我们使用 request.getSession()的时候,就 会使用 jsessionid

img

Session 通常有以下三种方式来维持 Web 客户端和 Web 服务器之间的 Session 会话:

(1)Cookies

一个 Web 服务器可以分配一个唯一的 session 会话 ID 作为每个 Web 客户端的 cookie,对于客户端的后续请求可以使用接收到的 cookie 来识别。

这可能不是一个有效的方法,因为很多浏览器不支持 cookie,所以我们建议不要使用这种方式来维持 session 会话。

(2)隐藏的表单字段

一个 Web 服务器可以发送一个隐藏的 HTML 表单字段,以及一个唯一的 session 会话 ID,如下所示:

<input type="hidden" name="sessionid" value="12345">

该条目意味着,当表单被提交时,指定的名称和值会被自动包含在 GET 或 POST 数据中。每次当 Web 浏览器发送回请求时,session_id 值可以用于保持不同的 Web 浏览器的跟踪。

这可能是一种保持 session 会话跟踪的有效方式,但是点击常规的超文本链接(不会导致表单提交,因此隐藏的表单字段也不支持常规的 session 会话跟踪。

(3)URL 重写

可以在每个 URL 末尾追加一些额外的数据来标识 session 会话,服务器会把该 session 会话标识符与已存储的有关 session 会话的数据相关联。

例如:

http://star.cc/file.htm;jsessionid=12345

session 会话标识符被附加为 jsessionid=12345,标识符可被 Web 服务器访问以识别客户端。

URL 重写是一种更好的维持 session 会话的方式,它在浏览器不支持 cookie 时能够很好地工作,

但是它的缺点是会动态生成每个 URL 来为页面分配一个 session 会话 ID,即使是在很简单的静态 HTML 页面中也会如此。

实现方法

HttpServletResponse 接口提供了重写 URL 的方法:public java.lang.String encodeURL(java.lang.String url)
该方法的实现机制为:
● 先判断当前的 Web 组件是否启用 Session,如果没有启用 Session,直接返回参数 url
● 再判断客户端浏览器是否支持 Cookie,如果支持 Cookie,直接返回参数 url;如果不支持 Cookie,就在参数 url 中加入 Session ID 信息,然后返回修改后的 url

如果需要使用重定向,可以使用 response.encodeRedirectURL() 来对 URL 进行编码。

1
2
3
4
5
6
7
8
//1.获取Session对象
HttpSession session = request.getSession();
//2.创建目标URL地址字符串
String url = "targetServlet";
//3.在目标URL地址字符串后面附加JSESSIONID的值
url = response.encodeURL(url);
//4.重定向到目标资源
response.sendRedirect(url);

HttpSession 对象

Servlet 还提供了 HttpSession 接口,该接口提供了一种跨多个页面请求或访问网站时识别用户以及存储有关用户信息的方式。

Servlet 容器使用这个接口来创建一个 HTTP 客户端和 HTTP 服务器之间的 session 会话。会话持续一个指定的时间段,跨多个连接或页面请求。

通过调用 HttpServletRequest 的公共方法 getSession() 来获取 HttpSession 对象:

1
2
3
4
5
6
7
8
9
10
// 1.这是常用的方式,从当前request中获取session,
// 如果获取不到session,则会自动创建一个session,并返回新创建的session;
// 如果获取到,则返回获取到的session;
HttpSession session = request.getSession();

// 2.这种方法和第一种一样
HttpSession session = request.getSession(true)

// 3.获取不到session的时候,不会自动创建session,而是会返回null。
HttpSession session = request.getSession(false)

需要在向客户端发送任何文档内容之前调用 request.getSession()

HttpSession 常用方法

  • public Object getAttribute(String name)
  • 该方法返回在该 session 会话中具有指定名称的对象,如果没有指定名称的对象,则返回 null。
  • public Enumeration getAttributeNames()
  • 该方法返回 String 对象的枚举,String 对象包含所有绑定到该 session 会话的对象的名称。
  • public long getCreationTime()
  • 该方法返回该 session 会话被创建的时间,自格林尼治标准时间 1970 年 1 月 1 日午夜算起,以毫秒为单位。
  • public String getId()
  • 该方法返回一个包含分配给该 session 会话的唯一标识符的字符串。
  • public long getLastAccessedTime()
  • 该方法返回客户端最后一次发送与该 session 会话相关的请求的时间自格林尼治标准时间 1970 年 1 月 1 日午夜算起,以毫秒为单位。
  • public int getMaxInactiveInterval()
  • 该方法返回 Servlet 容器在客户端访问时保持 session 会话打开的最大时间间隔,以秒为单位。
  • public void invalidate()
  • 该方法指示该 session 会话无效,并解除绑定到它上面的任何对象。
  • public boolean isNew()
  • 如果客户端还不知道该 session 会话,或者如果客户选择不参入该 session 会话,则该方法返回 true。
  • public void removeAttribute(String name)
  • 该方法将从该 session 会话移除指定名称的对象。
  • public void setAttribute(String name, Object value)
  • 该方法使用指定的名称绑定一个对象到该 session 会话。
  • public void setMaxInactiveInterval(int interval)
  • 该方法在 Servlet 容器指示该 session 会话无效之前,指定客户端请求之间的时间,以秒为单位。

案例:获取Session数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.star.session;

import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/index4")
public class Demo04 extends HttpServlet {
private static final long serialVersionUID = 1L;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doGet</span><span class="hljs-params">(HttpServletRequest request, HttpServletResponse response)</span> <span class="hljs-keyword">throws</span> ServletException, IOException </span>{
	<span class="hljs-comment">// 如果不存在 session 会话,则创建一个 session 对象</span>
	HttpSession session = request.getSession(<span class="hljs-keyword">true</span>);
	<span class="hljs-comment">// 获取 session 创建时间</span>
	Date createTime = <span class="hljs-keyword">new</span> Date(session.getCreationTime());
	<span class="hljs-comment">// 获取该网页的最后一次访问时间</span>
	Date lastAccessTime = <span class="hljs-keyword">new</span> Date(session.getLastAccessedTime());

	<span class="hljs-comment">// 设置响应内容类型</span>
	response.setContentType(<span class="hljs-string">"text/html;charset=UTF-8"</span>);
	PrintWriter out = response.getWriter();
	
	out.println(<span class="hljs-string">"&lt;p&gt;创建时间:"</span> + createTime + <span class="hljs-string">"&lt;/p&gt;"</span>);
	out.println(<span class="hljs-string">"&lt;p&gt;最后访问时间:"</span> + lastAccessTime + <span class="hljs-string">"&lt;/p&gt;"</span>);
	
	<span class="hljs-comment">// 设置日期输出的格式</span>
	SimpleDateFormat df = <span class="hljs-keyword">new</span> SimpleDateFormat(<span class="hljs-string">"yyyy-MM-dd HH:mm:ss"</span>);
	Integer uid = <span class="hljs-number">1000</span>;
	Integer count = <span class="hljs-number">0</span>;

	<span class="hljs-keyword">if</span> (session.getAttribute(<span class="hljs-string">"uid"</span>) == <span class="hljs-keyword">null</span>) {
		session.setAttribute(<span class="hljs-string">"uid"</span>, uid);
	}

	<span class="hljs-comment">// 检查网页上是否新的访问者</span>
	<span class="hljs-keyword">if</span> (session.isNew()) {
		out.println(<span class="hljs-string">"&lt;p&gt;新的访问者&lt;/p&gt;"</span>);
		session.setAttribute(<span class="hljs-string">"count"</span>, <span class="hljs-number">0</span>);
	} <span class="hljs-keyword">else</span> {
		count = session.getAttribute(<span class="hljs-string">"count"</span>)==<span class="hljs-keyword">null</span>?<span class="hljs-number">0</span>:(<span class="hljs-keyword">int</span>)session.getAttribute(<span class="hljs-string">"count"</span>);
		session.setAttribute(<span class="hljs-string">"count"</span>, count+<span class="hljs-number">1</span>);
	}

	out.println(<span class="hljs-string">"&lt;p&gt;UID:"</span> + uid + <span class="hljs-string">"&lt;/p&gt;"</span>);
	out.println(<span class="hljs-string">"&lt;p&gt;访问次数:"</span> + count + <span class="hljs-string">"&lt;/p&gt;"</span>);
}

}

Session 时效

Session 对象在服务器端不能长期保存,它是有时间限制的,超过一定时间没有被访问过的 Session 对象就应该释放掉,以节约内存。

所以 Session 的有效时间并不是从创建对 象开始计时,到指定时间后释放——而是从最后一次被访问开始计时,统计其“空闲”的时间。

(1)默认设置

在全局 web.xml 中能够找到如下配置:

1
2
3
4
5
6
<!-- ==================== Default Session Configuration ================= -->
<!-- You can set the default session timeout (in minutes) for all newly -->
<!-- created sessions by modifying the value below. -->
<session-config>
    <session-timeout>30</session-timeout>
</session-config>

说明 Session 对象默认的最长有效时间为 30 分钟。

(2)手工设置

1
2
session.setMaxInactiveInterval(int seconds) 
session.getMaxInactiveInterval() 

(3)强制失效

session.invalidate() 

(4)强制释放

  • Session 对象空闲时间达到了目标设置的最大值,自动释放
  • Web 应用卸载
  • 服务器进程停止

Session 活化和钝化

Session 机制很好的解决了 Cookie 的不足,但是当访问应用的用户很多时,服务器上就会创建非常多的 Session 对象,如果不对这些 Session 对象进行处理,那么在 Session 失效之前,这些 Session 一直都会在服务器的内存中存在。那么就,就出现了 Session 活化

和钝化的机制。

(1)Session 钝化:

Session 在一段时间内没有被使用时,会将当前存在的 Session 对象序列化到磁盘上,而不再占用内存空间。

(2)Session 活化:

Session 被钝化后,服务器再次调用 Session 对象时,将 Session 对象由磁盘中加载到内存中使用。

如果希望 Session 域中的对象也能够随 Session 钝化过程一起序列化到磁盘上,则对象的实现类也必须实现 java.io.Serializable 接口。不仅如此,如果对象中还包含其他对象的引用,则被关联的对象也必须支持序列化,否则会抛出异常:

Session 保持登录

当用户请求页面,用户第一次输入用户名和密码之后,

后台获取用户信息,通过查询数据库来验证用户信息是否正确,如果验证通过,则会开辟一块session空间来储存用户数据,并且同时生成一个cookie字符串,由后台返回给前台,

前台接收后,会把这个cookie字符串储存到浏览器的cookie空间中,这个cookie就相当于一把钥匙,可以打开后台存储对应用户信息的锁,当用户下一次请求的时候,客户端便会自动携带这个cookie去请求服务器,

服务器识别后,就会读取session中的用户信息,这样用户就可以直接访问,就不需要再输入用户名密码来验证身份了。

优缺点: 优点是:提升了用户体验,cookie和session的结合使用,比直接在客户端保存用户信息要相对安全;

缺点是:当服务器向浏览器传送cookie的时候,很容易被劫持,并不是绝对的安全,还有一点就是,在大型的项目中,服务器往往不只一台,如果第一次请求,用户信息被保存在了服务器1的session空间中,但是第二次请求被分流到了服务器2,这样就获取不到用户信息了,依然要重新登录。

案例:记住密码

表单

1
2
3
4
5
6
7
8
<div>
    <form action="login" method="get">
        账号:<input name="username" type="text"/><br>
        密码:<input name="password" type="password"><br>
        <input name="remember" type="radio"> 记住我<br>
        <input type="submit">
    </form>
</div>

Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    HttpSession session = request.getSession();
    String remember = request.getParameter("remember");
    Object attribute = null;
    if(remember.equals("true")){
        attribute = session.getAttribute("remember");
    }
response.setContentType(<span class="hljs-string">"text/html;charset=UTF-8"</span>);
<span class="hljs-keyword">if</span>(attribute != <span class="hljs-keyword">null</span>){
    <span class="hljs-comment">// "已经登录过!"; </span>
    response.sendRedirect(<span class="hljs-string">"/index"</span>);
}<span class="hljs-keyword">else</span>{
    String username = request.getParameter(<span class="hljs-string">"username"</span>);
    String password = request.getParameter(<span class="hljs-string">"password"</span>);
    <span class="hljs-keyword">if</span>(username.equals(<span class="hljs-string">"admin"</span>) &amp;&amp; password.equals(<span class="hljs-string">"admin"</span>)){
        session.setAttribute(<span class="hljs-string">"remember"</span>,<span class="hljs-keyword">true</span>);
        response.getWriter().write(<span class="hljs-string">"登录成功!"</span>); 
        response.setHeader(<span class="hljs-string">"refresh"</span>,<span class="hljs-string">"3;/index"</span>); 
    }<span class="hljs-keyword">else</span>{
   		response.getWriter().write(<span class="hljs-string">"用户名或者密码错误"</span>);
    }
}

}

退出登录

1
2
3
4
5
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    HttpSession session = request.getSession();
    session.removeAttribute("remember");
    response.sendRedirect("/login");
}

Session 解决重复提交

同一个表单中的数据内容多次提交到服务器。 危害:

  • 服务器重复处理信息,负担加重。
  • 如果是保存数据可能导致保存多份相同数据。

几种重复提交

(1)提交完表单后,直接刷新页面,会再次提交。

根本原因:Servlet 处理完请求以后,直接转发到目标页面。

这样整一个业务,只发送了一次请求,那么当你在浏览器中点击刷新按钮或者狂按 f5

会一直都会刷新之前的请求

解决方案:提交成功后使用重定向跳转到目标页面

(2)由于网速差等原因,服务器还未返回结果,连续点击提交按钮,会重 复提交。

根本原因:按钮可以多次点击

解决方案:通过 js,使得按钮只能提交一次。

1
2
3
$("#form1").submit(function(){ 
	$("#sub_btn").prop("disabled",true); 
})

(3)表单提交后,点击浏览器回退按钮,不刷新页面,点击提交按钮再次提交表单

根本原因:服务器并不能识别请求是否重复。

解决方案:使用 token 机制。

1、页面生成时,产生一个唯一的 token 值。将此值放入 session

2、表单提交时,带上这个 token 值。

3、服务端验证 token 值存在,则提交表单,然后移除此值。

验证 token 不存在,说明是之前验证过一次被移除了,所以是重复请求。不予处理

案例:重复提交

表单

1
2
3
4
5
6
7
8
9
<div>
    <h1>测试表单重复提交</h1>
    <form action="login" method="get">
        用户名:<input name="username" type="text"/>
        密码:<input name="password" type="password">
        <input name="token" value="<%=token%>">
        <input type="submit">
    </form>
</div>

Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // flag : 前端表单中附带标记,固定
    String flag = req.getParameter("flag");
    // 存储到 Session 中
    HttpSession session = req.getSession();
Object flag1 = session.getAttribute(<span class="hljs-string">"flag"</span>);

<span class="hljs-keyword">if</span> (flag1 != <span class="hljs-keyword">null</span>){
    <span class="hljs-comment">// 说明重复提交</span>
    System.out.println(<span class="hljs-string">"重复提交"</span>);
    <span class="hljs-keyword">return</span>;
}

<span class="hljs-comment">// 设置标记:表示正在处理</span>
session.setAttribute(<span class="hljs-string">"flag"</span>,flag);
<span class="hljs-comment">// 模拟处理过程</span>
<span class="hljs-keyword">try</span> {
    Thread.sleep(<span class="hljs-number">5000</span>);
} <span class="hljs-keyword">catch</span> (InterruptedException e) {
    e.printStackTrace();
}
<span class="hljs-comment">// 处理完成</span>
<span class="hljs-comment">// 设置标记:表示处理完成</span>
session.removeAttribute(<span class="hljs-string">"flag"</span>);

}

防止重复提交的核心就是让服务器有一个字段能来识别此次请求是否已经执行。

这个字段需要页面传递过来,因为只要回退回去的页面,字段都是一致的。不会变化, 通过这个特性我们想到了 token 机制来防止重复提交。

作业

实现购物车功能

    </article>
posted @ 2021-11-20 16:52  柠檬色的橘猫  阅读(38)  评论(0)    收藏  举报