•
HTTP
协议是一种无状态的协议
,
WEB
服务器本身不能识别出哪些请求是同一个浏览器发出的,浏览器的每一次请求都是完全孤立的
•
即使
HTTP1.1
支持持续连接,但当用户有一段时间没有提交请求,连接也会关闭。
•
怎么才能实现网上商店中的购物车呢:某个用户从网站的登录页面登入后,再进入购物页面购物时,负责处理购物请求的服务器程序必须知道处理上一次请求的程序所得到的用户信息。
•
作为
web
服务器,必须能够采用一种机制来唯一地标识一个用户,同时记录该用户的状态
1.会话和会话状态
•
WEB
应用中的会话是指一个客户端浏览器与
WEB
服务器之间连续发生的一系列请求和响应过程。
•
WEB
应用的会话状态是指
WEB
服务器与浏览器在会话过程中产生的状态信息,
借助会话状态,
WEB
服务器能够把属于同一会话中的一系列的请求和响应过程关联起来
。
2.如何实现有状态的会话
•
WEB
服务器端程序要能从大量的请求消息中区分出哪些请求消息属于同一个会话,即能识别出来自同一个浏览器的访问请求,这
需要浏览器对其发出的每个请求消息都进行标识
:属于同一个会话中的请求消息都附带同样的标识号,而属于不同会话的请求消息总是附带不同的标识号,这个标识号就称之为会话
ID
(
SessionID
)。
•
在
Servlet
规范中,常用以下两种机制完成会话跟踪
–
Cookie
–
Session
一、Cookie
/**
* @author changwen on 2016/11/13.
*/
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.ResourceBundle;
public class Cookie implements Cloneable, Serializable {
private static final long serialVersionUID = -6454587001725327448L;
private static final String TSPECIALS;
private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings");
private String name; //这个只有get方法
/*
下面几个属性都只有简单get,set方法
*/
private String value;
private String comment;
private String domain; //这个set方法有处理过
private int maxAge = -1;
private String path;
private boolean secure;
private int version = 0;
//需要了解,boolean类型的get方法是这样子public boolean isHttpOnly() {return isHttpOnly;}
private boolean isHttpOnly = false;
public Cookie(String name, String value) {
if(name != null && name.length() != 0) {
if(this.isToken(name) && !name.equalsIgnoreCase("Comment") && !name.equalsIgnoreCase("Discard")
&& !name.equalsIgnoreCase("Domain") && !name.equalsIgnoreCase("Expires")
&& !name.equalsIgnoreCase("Max-Age") && !name.equalsIgnoreCase("Path")
&& !name.equalsIgnoreCase("Secure") && !name.equalsIgnoreCase("Version")
&& !name.startsWith("$")) {
this.name = name;
this.value = value;
} else {
String errMsg = lStrings.getString("err.cookie_name_is_token");
Object[] errArgs = new Object[]{name};
errMsg = MessageFormat.format(errMsg, errArgs);
throw new IllegalArgumentException(errMsg);
}
} else {
throw new IllegalArgumentException(lStrings.getString("err.cookie_name_blank"));
}
}
private boolean isToken(String value) {
int len = value.length();
for(int i = 0; i < len; ++i) {
char c = value.charAt(i);
if(c < 32 || c >= 127 || TSPECIALS.indexOf(c) != -1) {
return false;
}
}
return true;
}
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException var2) {
throw new RuntimeException(var2.getMessage());
}
}
static {
if(Boolean.valueOf(System.getProperty("org.glassfish.web.rfc2109_cookie_names_enforced", "true")).booleanValue()) {
TSPECIALS = "/()<>@,;:\\\"[]?={} \t";
} else {
TSPECIALS = ",; ";
}
}
public void setDomain(String domain) {
this.domain = domain.toLowerCase(Locale.ENGLISH);
}
public String getDomain() {
return this.domain;
}
public String getName() {
return this.name;
}
}
Servlet API
中提供了一个
javax.servlet.http.Cookie
类来封装
Cookie
信息,它包含有生成
Cookie
信息和提取
Cookie
信息的各个属性的方法。
•
HttpServletResponse
接口中定义了一个
addCookie
方法,它用于在发送给浏览器的
HTTP
响应消息中增加一个
Set-Cookie
响应头字段。
•
HttpServletRequest
接口中定义了一个
getCookies
方法,它用于从
HTTP
请求消息的
Cookie
请求头字段中读取所有的
Cookie
项。
1-1.Cookie 的发送
•
1
.
创建
Cookie
对象
•
2.
设置最大时效
•
3.
将
Cookie
放入到
HTTP
响应报头
–
如果创建了一个
cookie
,并将他发送到浏览器,
默认情况下它是一个
会话级别
的
cookie;
存储在浏览器的内存中,用户退出浏览器之后被删除
。
若希望浏览器将该
cookie
存储在磁盘上,则需要使用
maxAge
,并给出一个以秒为单位的时间。
将最大时效设为
0
则是命令浏览器删除该
cookie
。
–
发送
cookie
需要使用
HttpServletResponse
的
addCookie
方法,将
cookie
插入到一个
Set-Cookie
HTTP
响应报头中。由于这个方法并不修改任何之前指定的
Set-Cookie
报头,而是创建新的报头,因此将这个方法称为是
addCookie
,而非
setCookie
。
2.会话 cookie 和持久 cookie 的区别
•
如果
不设置过期时间,则表示这个
cookie
生命周期为浏览器会话期间,只要关闭浏览器窗口,
cookie
就消失了。这种生命期为浏览器会话期的
cookie
被称为
会话
cookie
。
会话
cookie
一般不保存在硬盘上而是保存在内存里
。
•
如果设置了过期时间,浏览器就会把
cookie
保存到硬盘上,关闭后再次打开浏览器,这些
cookie
依然有效直到超过设定的过期时间。
•
存储在硬盘上的
cookie
可以在不同的浏览器进程间共享,比如两个
IE
窗口。而对于保存在内存的
cookie
,不同的浏览器有不同的处理方式。
1-3.应用
1•
自动登录:不
需要填写用户名和密码等信息,可以自动登录到系统
2•显示最近浏览的内容
3•
跟踪用户上次访问站点的时间
•
功能
: 帮助网站实现提示客户端计算机上次访问网站的时间
•
实现原理:
将每一个会话作为一次访问过程,将每次会话的开始时间作为每次访问网站的时间,然后将这个时间以
Cookie
的形式存储到客户端的计算机中,客户端进行下次访问时通过该
Cookie
回传上次访问站点的时间值。
为了让
Cookie
信息在客户端浏览器或计算机关闭后仍然保持存在,
Cookie
的保存时间被设置为了一年。
Cookie 的 作用范围 : 可以作用当前目录和当前目录的子目录. 但不能作用于当前目录的上一级目录。可以通过 setPath 方法来设置 Cookie 的作用范围, 其中 / 代表站点的根目录.
二、Session
•
session
,中文经常翻译为会话,其本来的含义是指有始有终的一系列动作
/
消息,比如打电话是从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个
session
。
•
session
在
Web
开发环境下的语义又有了新的扩展,它的含义是指
一类用来在客户端与服务器端之间保持状态的解决方案。有时候
Session
也用来指这种解决方案的存储结构。
1.Session 机制
•
session
机制采用的是
在
服务器
端保持
HTTP
状态信息的方案
。
•
服务器使用一种类似于散列表的结构
(
也可能就是使用散列表
)
来保存信息。
•
当程序需要为某个客户端的请求创建一个
session
时,服务器首先检查这个
客户端的请求里
是否包含了一个
session
标识
(
即
sessionId
),
如果已经包含一个
sessionId
则说明以前已经为此客户创建过
session
,服务器就按照
sessionid
把这个
session
检索出来使用
(
如果检索不到,可能会新建一个,这种情况可能出现在服务端已经删除了该用户对应的
session
对象,但用户人为地在请求的
URL
后面附加上一个
JSESSION
的参数
)
。如果客户请求不包含
sessionId
,则为此客户创建一个
session
并且生成一个与此
session
相关联的
sessionId
,
这个
sessionid
将在本次响应中返回给客户端保存
。
2.保存 sessionid 的几种方式
•
保存
sessionid
的方式可以采用
cookie
,这样在交互过程中浏览器可以
自动
的按照规则把这个标识发送给服务器。
•
由于
cookie
可以被人为的禁用,必须有其它的机制以便在
cookie
被禁用时仍然能够把
sessionid
传递回服务器,经常采用的一种技术叫做
URL
重写
,
就是把
sessionid
附加在
URL
路径的后面
,附加的方式也有两种,一种是作为
URL
路径的附加信息,另一种是作为查询字符串附加在
URL
后面。网络在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个
sessionid
。
3.Session cookie
•
session
通过
SessionID
来区分不同的客户
,session
是以
cookie
或
URL
重写为基础的,
默认使用
cookie
来实现
,
系统会创造一个名为
JSESSIONID
的输出
cookie
,这称之为
sessioncookie
,
以区别
persistentcookies
(
也就是我们通常所说的
cookie),
sessioncookie
是存储于浏览器内存中的,并不是写到硬盘上的
,通常看不到
JSESSIONID
,但是当把浏览器的
cookie
禁止后,
web
服务器会采用
URL
重写的方式传递
Sessionid
,这时地址栏看到
•
session cookie
针对某一次会话而言,会话结束
sessioncookie
也就随着消失了,而
persistentcookie
只是存在于客户端硬盘上的一段文本。
•
关闭浏览器,只会是浏览器端内存里的
sessioncookie
消失,但不会使保存在服务器端的
session
对象消失,同样也不会使已经保存到硬盘上的持久化
cookie
消失。
4.Session 生命周期,即创建 与删除
•
一
个常见的错误是以为
session
在有客户端访问时就被创建,不一定,1、如果是第一个页面,且JSP的page指定的session被设置为false。2、若当前JSP不是客户端访问的当前WEB应用的第一个资源,且其他页面已经创建一个HttpSession对象,则当前JSP页面会返回一个会话的HttpSession对象,而不会创建一个新的HttpSession对象。然而事实是直到某
server
端程序
(
如
Servlet
)
调用
HttpServletRequest.getSession
(true)
或者
HttpServletRequest.getSession
()
这样的语句时才会被创建。
•session在下列情况下被删除:
–
A
.程序调用
HttpSession.invalidate
()
–
B
.距离上一次收到客户端发送的
sessionid
时间间隔超过了
session
的最大有效时间 返回最大时效: getMaxInactiveInterval() 单位是秒 设置最大时效: setMaxInactiveInterval(int interval) 可以在 web.xml 文件中配置 Session 的最大时效, 单位是分钟. 30
–
C
.服务器进程被停止
•
注意
:关闭浏览器只会使存储在客户端浏览器内存中的
sessioncookie
失效,不会使服务器端的
session
对象失效。
HttpSession 的生命周期:
1.
什么时候创建
HttpSession
对象
1.
是否浏览器访问服务端的任何一个
JSP
或
Servlet
,服务器都会立即创建一个
HttpSession
对象呢?不一定
。若当前的
JSP
(或
Servlet
)是客户端访问的当前
WEB
应用的第一个资源,且
JSP
的
page
指定的
session
属性
值为
false,
则服务器就不会为
JSP
创建一个
HttpSession
对象;若当前
JSP
不是客户端访问的当前
WEB
应用的第一个资源,且其他页面已经创建一个
HttpSession
对象,则当前
JSP
页面会返回一个会话的
HttpSession
对象,而不会创建一个新的
HttpSession
‘对象
2.
session
=
“false“
到底表示什么意思?当前
JSP
页面禁用
session
隐含变量!但可以使用其他的显式的
HttpSession
对象
3.
对于
Serlvet
而言:若
Serlvet
是客户端访问的第一个
WEB
应用的资源,则只有调用了
request.getSession
()
或
request.getSession
(true)
才会创建
HttpSession
对象
5.两个浏览器窗口访问应用程序会使用同一个 session
•
通常
sessioncookie
是不能跨窗口使用的
(IE 8
版本以前
)
,当你新开了一个浏览器窗口进入相同页面时,系统会赋予你一个新的
sessionid
,这样信息共享的目的就达不到了。
•
此时可以先把
sessionid
保存在
persistentcookie
中
(
通过设置
cookie
的最大有效时间
)
,然后在新窗口中读出来,就可以得到上一个窗口的
sessionid
了,这样通过
sessioncookie
和
persistentcookie
的结合就可以实现了跨窗口的会话跟踪。
6.Session 的超时管理
•
WEB
服务器无法判断当前的客户端浏览器是否还会继续访问,也无法检测客户端浏览器是否关闭,所以,即使客户已经离开或关闭了浏览器,
WEB
服务器还要保留与之对应的
HttpSession
对象。
•
随着时间的推移而不断增加新的访问客户端,
WEB
服务器内存中将会因此积累起大量的不再被使用的
HttpSession
对象,并将最终导致服务器内存耗尽。
•
WEB
服务器采用“超时限制”的办法来判断客户端是否还在继续访问
,如果某个客户端在一定的时间之内没有发出后续请求,
WEB
服务器则认为客户端已经停止了活动,结束与该客户端的会话并将与之对应的
HttpSession
对象变成垃圾。
•
如果客户端浏览器超时后再次发出访问请求,
WEB
服务器则认为这是一个新的会话的开始,将为之创建新的
HttpSession
对象和分配新的会话标识号。
•
会话的超时间隔可以在
web.xml
文件中设置
,其默认值由
Servlet
容器定义。
config >
30
config>
public interface HttpSession {
String getId(); // 获取session id
boolean isNew(); //session是否是新的
void setMaxInactiveInterval(int var1); //设置session最大时效
int getMaxInactiveInterval(); // 获取session最大时效
long getCreationTime(); // 创建时间
long getLastAccessedTime(); //上次访问时间
/*最重要的两个方法*/
Object getAttribute(String var1); // 获取属性
void setAttribute(String var1, Object var2);
Enumeration getAttributeNames();
void removeAttribute(String var1);
void invalidate(); //使session无效
ServletContext getServletContext();
/** @deprecated */
HttpSessionContext getSessionContext();
/** @deprecated */
Object getValue(String var1);
/** @deprecated */
String[] getValueNames();
/** @deprecated */
void putValue(String var1, Object var2);
/** @deprecated */
void removeValue(String var1);
}
7.利用 URL 重写实现 Session 跟踪
•
Servlet
规范中引入了一种补充的会话管理机制,
它允许不支持
Cookie
的浏览器也可以与
WEB
服务器保持连续的会话
。这种补充机制要求在响应消息的实体内容中必须包含下一次请求的超链接,并将会话标识号作为超链接的
URL
地址的一个特殊参数。
•
将会话标识号以参数形式附加在超链接的
URL
地址后面的技术称为
URL
重写
。如果在浏览器不支持
Cookie
或者关闭了
Cookie
功能的情况下,
WEB
服务器还要能够与浏览器实现有状态的会话,就必须对所有可能被客户端访问的请求路径(包括超链接、
form
表单的
action
属性设置和重定向的
URL
)进行
URL
重写。
•
HttpServletResponse
接口中定义了两个用于完成
URL
重写方法:
- encodeURL
方法
-
encodeRedirectURL
方法
8.应用
9. 相对路径和绝对路径: 开发时建议编写“绝对路径”:写绝对路径肯定没有问题,但写相对路径却可能有问题1). 为什么要解决相对路径的问题: 在有一个 Servlet 转发页面的情况下, 会导致相对路径的混乱. a.jsp: To B Page2 ToBServlet: request.getRequestDispatcher("/dir/b.jsp").forward(request, response); 注意, 此时点击 To B Page2 超链接后的浏览器的地址栏的值: http://localhost:8989/day_36/ToBServlet, 实际显示的是dir 路径下的 b.jsp,而 b.jsp 页面有一个超链接: TO C Page . 默认情况下, c.jsp 应该和 b.jsp 在同一路径下. 此时点击超链接 将在浏览器地址栏显示: http://localhost:8989/day_36/c.jsp. 但在根目录下并没有 c.jsp, 所以会出现路径混乱的问题.2). 使用绝对路径会解决以上的问题: 绝对路径: 相对于当前 WEB 站点根目录的路径.在当前 WEB 应用的所有的路径前都添加 contextPath 即可. http://localhost:8989/httpsession/c.jsp: - http://localhost:8989/ 是 WEB 站点的根目录, - /httpsession_36 是 contextPath, - /c.jsp 是相对于当前 WEB 应用的一个文件路径. 我们需要在当前 WEB 应用的任何的路径下都添加上 contextPath, 即可. 比如: - To B Page2 需改为: To B Page2 - response.sendRedirect("a.jsp"); 需改为: response.sendRedirect(request.getContextPath() + "/a.jsp"); -
需改为:
3). 在 JavaWEB 应用中 / 代表的是什么: ①.当前WEB应用的根路径:http://localhost:8989/httpsession/ 若/需要交给Servlet容器来处理
- 请求转发时:request.getRequestDispatcher("/path/b.jsp").forward(request,response);
- web.xml文件中映射Servlet访问路径时
TestServlet /testServlet ②.WEB站点的根路径:http://localhost:8989/ 若 / 直接交由浏览器解析
- 超链接:
To B Page - 表达式中的action:
- 做请求重定向的时候:response.sendRedirect("/a.jsp");
总结: / 什么时候代表站点的根目录, 什么时候代表当前 WEB 应用的根目录
若 / 需要服务器进行内部解析, 则代表的就是 WEB 应用的根目录. 若是交给浏览器了, 则 / 代表的就是站点的根目录 若 / 代表的是 WEB 应用的根目录, 就不需要加上 contextPath 了. 4). 如何获取 contextPath: ServletContext: getContextPath()
HttpServletRequest: getContextPath()
10.避免表单的重复提交
•
调用
RequestDispatcher.forward
()
方法,浏览器所保留的
URL
是先前的表单提交的
URL
,此时点击”刷新”
,
浏览器将再次提交用户先前输入的数据,引起重复提交
•
如果采用
HttpServletResponse.sendRedirct
()
方法将客户端重定向到成功页面,将不会出现重复一条问题
•利用 Session 防止表单重复提交
•
包含
有
FORM
表单的页面必须通过一个服务器程序动态产生,服务器程序为每次产生的页面中的
FORM
表单都分配一个唯一的随机标识号,并在
FORM
表单的一个隐藏字段中设置这个标识号,同时在当前用户的
Session
域中保存这个标识号。
•
当用户提交
FORM
表单时,负责接收这一请求的服务器程序比较
FORM
表单隐藏字段中的标识号与存储在当前用户的
Session
域中的标识号是否相同,如果相同则处理表单数据,处理完后清除当前用户的
Session
域中存储的标识号。在下列情况下,服务器程序将忽略提交的表单请求:
-
当前用户的
Session
中不存在表单标识号
-
用户提交的表单数据中没有标识号字段
-
存储在当前用户的
Session
域中的表单标识号与表单数据中的标识号不同
•
浏览器只有重新向
WEB
服务器请求包含
FORM
表单的页面时,服务器程序才又产生另外一个随机标识号,并将这个标识号保存在
Session
域中和作为新返回的
FORM
表单中的隐藏字段值。
3. 表单的重复提交
1). 重复提交的情况:
①. 在表单提交到一个 Servlet, 而 Servlet 又通过
请求转发 的方式响应一个 JSP(HTML) 页面,
此时地址栏还保留着 Serlvet 的那个路径, 在响应页面点击 "刷新"
②. 在响应页面没有到达时重复点击 "提交按钮".
③. 点击 "返回", 再点击 "提交"
2). 不是重复提交的情况: 点击 "返回", "刷新" 原表单页面, 再 "提交"。
3). 如何避免表单的重复提交 : 在表单中做一个标记, 提交到 Servlet 时, 检查标记是否存在且是否和预定义的标记一致, 若一致, 则受理请求,
并销毁标记, 若不一致或没有标记, 则直接响应提示信息: "重复提交"
①. 仅提供一个隐藏域:
. 行不通: 没有方法清除固定的请求参数.
②. 把标记放在 request 中. 行不通, 因为表单页面刷新后, request 已经被销毁, 再提交表单是一个新的 request.
③. 把标记放在 session 中. 可以!
在原表单页面, 生成一个随机值 token
在原表单页面, 把 token 值放入 session 属性中
在原表单页面, 把 token 值放入到 隐藏域 中.
在目标的 Servlet 中: 获取 session 和 隐藏域 中的 token 值
比较两个值是否一致: 若一致, 受理请求, 且把 session 域中的 token 属性清除
若不一致, 则直接响应提示页面: "重复提交"
10.利用 Session 实现一次性验证码
•
一次性
验证码的主要目的就是为了限制人们利用工具软件来
暴力
猜测密码,其原理与利用
Session
防止表单重复提交的原理基本一样,只是将表单标识号变成了验证码的形式,并且要求用户将提示的验证码手工填写进一个表单字段中,而不是通过表单的隐藏字段自动回传给服务器。
•
服务器程序接收到表单数据后,首先判断用户是否填写了正确的验证码,只有该验证码与服务器端保存的验证码匹配时,服务器程序才开始正常的表单处理流程。
•
密码猜测工具要逐一尝试每个密码的前题条件是先输入正确的验证码,而验证码是一次性有效的,这样基本上就阻断了密码猜测工具的自动地处理过程
。
使用 HttpSession 实现验证码
1). 基本原理: 和表单重复提交一致:
在原表单页面, 生成一个验证码的图片, 生成图片的同时, 需要把该图片中的字符串放入到 session 中.
在原表单页面, 定义一个文本域, 用于输入验证码.
在目标的 Servlet 中: 获取 session 和 表单域 中的 验证码的 值
比较两个值是否一致: 若一致, 受理请求, 且把 session 域中的 验证码 属性清除
若不一致, 则直接通过重定向的方式返回原表单页面, 并提示用户 "验证码错误"