一、Web应用中的会话与会话状态
- 会话与会话状态简介
Web应用中的会话过程是指一个客户端浏览器与Web服务器之间
连续发生的一系列请求和响应过程。
Web应用的会话状态是指Web服务器与浏览器在会话过程中产生的状态信息,借助会话状态,
Web服务器能够把属于同一个会话中的一系列的请求和响应过程关联起来,
使得它们之间可以互相依赖和传递信息。 - 如何实现有状态的会话
由于HTTP协议本身不具备有会话状态,这就需要浏览器对其发出的每个请求消息都进行标识,
属于同一个会话中的请求消息都附带同样的标识号,而属于不同会话的请求消息
总是附带不同的标识号,这个标识号就称之为会话ID(SessionID)。
会话ID可以通过一种称之为Cookie的技术在请求消息中进行传递,
也可以作为请求URL的附加参数进行传递。会话ID是Web服务器为每个客户端浏览器
分配的一个唯一代号,它通常是在Web服务器接收到某个浏览器的第一次访问时产生的,
并且随同响应消息一道发送给浏览器,浏览器则在以后发出的访问请求消息中都顺带
SessionID。这样,Web服务器程序就能识别出这次访问请求是来自哪个客户端浏览器了。
二、Cookie
- 什么是Cookie
Cookie是在浏览器访问Web服务器的某个资源时,
由Web服务器在HTTP响应消息头中附带传送给浏览器的一片数据,
Web服务器传送给各个客户端浏览器的数据是可以各不相同的。
Cookie最先是由Web服务器发出的,是否发送Cookie和
发送的Cookie的具体内容,完全是由Web服务器决定的。
Web服务器通过在HTTP响应消息中增加Set-Cookie响应头字段
将Cookie信息发送给浏览器,浏览器则通过在HTTP请求消息中
增加Cookie请求头字段将Cookie回传给Web服务器。
一个Cookie只能标识一种信息,它至少含有一个标识该信息的
名称(name)和设置值(value)。
一个Web站点可以给一个Web浏览器发送多个Cookie,这样,
在Web浏览器和Web服务器之间就可以使用多个Cookie来传递多种信息。
Cookie除了有名称和设置值外,它还可以有一些其他的附加属性,
如Cookie的有效时间Max-Age。
浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,
每个Cookie的大小限制为4KB。 - Set-Cookie2响应头字段
Set-Cookie2头字段中设置的cookie内容是具有一定格式的字符串,它必须
以Cookie的名称和设置值开头,格式为“名称=值”,后面可以加上0个或多个
以分号(;)和空格分隔的其他可选属性,属性格式一般为“属性名=值”,如
Set-Cookie2: user=xxx; Version=1; path=/
除了“名称=值”对必须位于最前面外,其他的可选属性的先后顺序可以任意。
Cookie的名称只能由普通的英文ASCII字符组成。
下面是Set-Cookie2头字段中的各个属性的介绍。
1、Comment=value
Web服务器可以设置Commont属性来说明这个Cookie的作用和对用户的
私人信息可能造成的影响等,value部分的字符必须使用UTF-8。
2、Discard
Discard属性没有属性值部分,它用于通知浏览器关闭时
无条件地删除该Cookie信息。如果没有设置这个属性,
浏览器删除Cookie的行为由Max-Age属性决定。
3、Domain=value
Domain属性用于指定Cookie在哪个域中有效,Domian属性的默认值为
当前主机名,Domain属性设置值是不区分大小写的。
4、Max-Age=value
Max-Age属性用于指定Cookie在客户端保持有效的时间,
其中的value部分是以秒为单位的十进制整数。
5、Path=value
Path属性用于指定Cookie对服务器上的哪个URL目录和其子目录有效。这个
属性的默认值是产生Set-Cookie2头字段时的哪个请求URL地址所在目录。
Path属性设置是区分大小写的。
6、Port[="portlist"]
Port属性用于指定浏览器通过哪些端口访问Web服务器时,Cookie才有效。
端口号列表中的每个端口号之间用逗号(,)分隔,整个端口号列表必须用
双引号("")引起来。默认值是任意端口。
7、Secure
Secure属性没有属性值部分,它用于通知浏览器在回传这个Cookie信息时,
应使用安全的方式访问服务器,以保护Cookie中的机密和认证等方面的信息,
否则,浏览器不能访问这个被储存的Cookie信息。如果没有设置Secure属性,
浏览器可以用非安全的方式发送这个Cookie信息。
8、Version=value
Version属性用于指定Cookie内容所遵循的版本格式,
value部分为一个十进制的整数,目前只有Version=1可以使用。
如果浏览器接收到的Set-Cookie2头字段中设置的Cookie与以前的存储的Cookie的
名称相同,并且Domain属性值(不区分大小写)和Path属性值(区分大小写)
都相等,那么就可以认为这两个Cookie代表的是同一个信息,新的Cookie将替换
以前的Cookie。如果服务器想让浏览器端保存的某个Cookie失效,可以重新发送
包含该Cookie的Set-Cookie2头字段,并在设置值部分增加Max-Age=0属性。
一个响应头中可以包含多个Set-Cookie2头字段来设置多个Cookie,也可以用一个
Set-Cookie2头字段来设置多个Cookie。 - Cookie请求头字段
如果浏览器愿意接受Web服务器发送过来的Cookie信息,它将存储Cookie信息,
并在以后对该Web服务器的每次访问请求中都使用一个Cookie请求头字段
将Cookie的信息回送给Web服务器。多个Cookie信息可以且只能通过一个
Cookie请求头字段回送给Web服务器。
浏览器每次向服务器发送访问请求时,它都需要根据下面的几个规则,决定是否发送
Cookie请求头字段,以及在Cookie请求头字段中附带哪些Cookie信息。
1、请求的主机名是否与某个存储的Cookie的Domain属性匹配;
2、请求的端口号是否在该Cookie的Port属性列表中;
3、请求的资源路径是否在该Cookie的Path属性指定的目录及子目录中;
4、该Cookie的有效期是否已过。
假如有多个存储的Cookie满足上面的规则,浏览器将把这些Cookie信息都添加到
一个Cookie请求头字段中,每个Cookie之间用逗号(,)或分号(;)分隔。
在Cookie请求头字段中,除了必须有“名称=值”的设置外,还可以有Version、Path、
Domain、Port等几个属性。在Version、Path、Domain、Port等属性名之前,
都要增加一个“$”字符作为前缀。Version属性只能出现一次,
且要位于Cookie请求头字段设置值的最前面。
如果需要设置某个Cookie信息的Path、Domain、Port等属性,它们必须位于
该Cookie信息的“名称=值”设置之后。Path属性指向子目录的Cookie要排在
Path属性指向父目录的Cookie之前。
Cookie: $Version=1; Course=Java; $Path=/xxx/lesson; Course=vc; $Path=/xxx
三、在Servlet程序中使用Cookie
- Servlet API中提供一个javax.servlet.http.Cookie类来封装Cookie信息。
- Cookie类
1、构造方法
Cookie类只有一个如下的构造方法:
public Cookie(java.lang.String name, java.lang.String value)
其中的两个参数分别代表Cookie的名称和值,名称中不能包含任何空白字符,
逗号、分号,并且不能以$字符开头。
2、getName方法
getName方法用于返回Cookie的名称
3、setValue与getValue方法
setValue与getValue方法分别用于设置和返回Cookie的值。
4、setMaxAge与getMaxAge方法
setMaxAge与getMaxAge方法分别用于设置和返回Cookie在浏览器客户机上
保持有效的秒数。如果设置值为0,则通知浏览器立即删除这个Cookie项;
如果设置值为负数,则表示在浏览器关闭时删除这个Cookie项;
如果设置值为正数,浏览器将该Cookie项保留在客户机的硬盘上,
这个Cookie对该客户机上的所有浏览器进程都有效,即使关闭浏览器和
计算机以后,只要设置的时间还没到期,该Cookie就一直有效。
5、setPath和getPath方法
setPath和getPath方法分别用于设置和返回该Cookie项的有效目录路径。
6、setDomain和getDomain方法
setDomain和getDomain方法分别用于设置和返回该Cookie项的有效域。
7、setVersion与getVersion方法
setVersion与getVersion方法分别用于设置和返回该Cookie项采用的协议版本。
8、setComment和getComment方法
setComment和getComment方法分别用于设置和返回该Cookie项的注解部分。
9、setSecure与getSecure方法
setSecure与getSecure方法分别用于设置和返回该Cookie项
是否只能使用安全的协议传送。 - HttpServletResponse.addCookie方法
HttpServletResponse接口中定义了一个addCookie方法,
它用于在发送给浏览器的HTTP响应消息中增加一个Set-Cookie响应头字段,
Set-Cookie头字段的设置值由传递给该方法的一个Cookie对象生成。
在Servlet程序中可以多次调用addCookie方法来设置多个Set-Cookie头字段,
尽量不要使用HttpServletResponse.stHeader方法来直接设置Set-Cookie头字段。 - HttpServletRequest.getCookies方法
HttpServletRequest接口中定义了一个getCookies方法,
它用于从HTTP请求消息的Cookie请求头字段中读取所有的Cookie项,
将每个Cookie项封装成各自的Cookie对象后,存储在一个数组中返回。
在Servlet程序中不应使用HttpServletRequest.getHeader方法来直接读取Cookie
请求头字段,而应该使用HttpServletRequest.getCookies方法来读取Cookie信息。
四、Session
- 什么是Session
Cookie是在客户端保持状态的方案,而Session则是在服务器端保持状态的方案。
在Session中有一个会话标识号,由于客户端需要接收、记忆和回送Session的
会话标识号,因此,Session可以且通常是借助Cookie来传递会话标识号。 - Session的跟踪机制
Servlet API规范中定义了一个HttpSession接口,HttpSession接口定义了
各种管理和操作会话状态的方法。
客户端访问某个特殊的Servlet程序,并且这个Servlet程序决定与客户端开启
一个会话时,Web应用程序才会创建一个与该客户端对应的HttpSession对象,
并为这个HttpSession对象分配一个独一无二的会话标识号(SessionID),
然后在响应消息中将这个会话标识号传递给客户端。客户端需要记住会话
标识号,并在后续的每次访问请求中都把这个会话标识号传送给Web服务器,
Web服务器端程序依据回传的会话标识号就知道这次请求是哪个客户端发出的,
从而选择与之对应的HttpSession对象。
HttpSession接口中定义了一个setAttribute方法来将对象存储到HttpSession中,
还定义了一个getAttribute方法来检索存储在HttpSession对象中的对象,
由于属于同一个会话的各个请求的处理程序共享同一个HttpSession对象,
所以,存储进HttpSession对象中的对象可以被属于同一个会话的各个请求的
处理程序共享。Session是实现网上商城的购物车的最佳方案。 - Session的超时管理
Web服务器采用“超时限制”的办法来判断客户端是否还在继续访问,
如果某个客户端在一定的时间之内没有发出后续请求,Web服务器则认为
客户端已经停止了活动,结束与该客户端的会话并将与之对应的HttpSession
对象变成垃圾,等待垃圾回收器将其彻底从内存中清除。
会话的超时间隔可以在web.xml文件中设置,其默认值由Servlet容器定义。
在<tomcat安装目录>\conf\web.xml文件中,可以找到如下一段配置信息:
<session-config>
<session-timeout>30</session-timeout>
</session-config>
这里设置的时间值是以分钟为单位的,
如果将<session-timeout>元素中的时间值设置成0或一个负数,
则表示会话永不超时。由于<tomcat安装目录>\config\web.xml
文件对站点内的所有Web应用程序都起作用,如果想单独设置某个Web
应用程序的会话超时间隔,应该在其自己的web.xml文件中进行设置。 - HttpSession接口中的方法
1、getId方法
getId方法用于返回与当前HttpSession对象关联的会话标识号。
2、getCreationTime方法
getCreationTime方法用于返回当前HttpSession对象的创建时间,
返回值是一个自1970年1月1日0点0分0秒开始计算的毫秒数。
3、getLastAccessedTime方法
getLastAccessedTime方法用于返回当前HttpSession对象的上一次被访问的
时间,返回值是一个自1970年1月1日0点0分0秒开始计算的毫秒数。
4、setMaxInactiveInterval方法
setMaxInactiveInterval方法用于设置当前HttpSession对象可空闲的
以秒为单位的最长时间,也就是修改当前会话的默认超时时间。
如果将会话超时时间设置成一个负数,则表示会话永不超时。
5、getMaxInactiveInterval方法
getMaxInactiveInterval方法用于返回当前HttpSession对象可空闲的
以秒为单位的最长时间,也就是当前会话的超时间隔。
6、isNew方法
isNew方法返回当前HttpSession对象是否是新创建的,如果是则返回true,
否则返回false。在下列情况下,HttpSession对象都需要新建,
isNew方法返回true:
(1)请求消息中没有通过任何方式返回会话标识号。
(2)请求消息中通过某种方式返回了会话标识号,但是返回的会话标识号
与当前的HttpSession对象中保存的会话标识号不匹配。
7、invalidate方法
invalidate方法用于强制当前HttpSession对象无效,Web服务器则可立即释放
该HttpSession对象,而不用等到超时后才释放该HttpSession对象。
8、getServletContext方法
getServletContext方法用于返回当前HttpSession对象
所属于的Web应用程序对象。
9、setAttribute方法
setAttribute方法用于将一个对象与一个名称关联后
存储进当前的HttpSession对象中。
10、getAttribute方法
getAttribute方法用于从当前HttpSession对象中返回指定名称的属性对象。
11、removeAttribute方法
removeAttribute方法用于从当前HttpSession对象中删除指定名称的属性。
12、getAttributeNames方法
getAttributeNames方法用于返回一个包含当前HttpSession对象中的
所有属性名的Enumeration对象。 - HttpServletRequest接口中的Session方法
1、getSession方法
getSession方法用于返回与当前请求相关的HttpSession对象,
该方法有两种重载形式:
public HttpSession getSession(boolean create)
public HttpSession getSession()
当调用这两个方法时,如果客户端的请求消息中包含有SessionID,
服务器检索并返回与这个SessionID对应的HTTPSession对象。如果请求
消息中没有SessionID或服务端不存在与SessionID对应的HttpSession对象,
第一个方法根据传递的参数来决定是否创建新的HttpSession对象,
否则不创建新的HttpSession对象,而是返回null。
第二个方法相当于第一个方法的参数为true时的情况,
在相关的HttpSession对象不存在时总是创建新的HttpSession对象。
2、isRequestedSessionIdValid方法
isRequestedSessionIdValid方法返回一个指示请求
消息中的SessionID是否有效的boolean值。
3、isRequestedSessionIdFromCookie方法
isRequestedSessionIdFromCookie方法用于判断SessionID是否是
通过请求消息中的Cookie传递过来的。
4、isRequestedSessionIdFromURL方法
isRequestedSessionIdFromURL方法用于判断SessionID是否是
通过请求消息的URL参数传递过来的。 - application域与session域属性的比较
同一个Web应用程序中的所有Servlet程序得到的是同一个ServletContext对象,
所以存储在ServletContext对象中的属性可以被所有的Servlet程序访问
和共享,而不管访问来自哪个客户端。
存储在HttpSession对象中的属性也可以被所有的Servlet程序访问,
但仅仅是来自同一个客户端的一组访问才能共享同一个HttpSession对象,
来自不同客户端的各组访问共享的HttpSession对象是不同的。 - 利用Cookie实现Session跟踪
如果Web服务器处理某个访问请求时创建了新的HttpSession对象,它将把会话
标识号作为一个Cookie项加入到响应消息中,通常情况下,浏览器在随后
发出的访问请求中又将会话标识号以Cookie的形式回传给Web服务器,
Web服务器端程序依据回传的会话标识号就知道以前已经为该客户端
创建了HttpSession对象,不必再为该客户端创建新的HttpSession对象,
而是直接使用与该会话表示号匹配的HttpSession对象,通过这种方式
就实现了对同一个客户端的会话状态的跟踪。 - 利用URL重写实现Session跟踪
考虑到浏览器有可能不支持Cookie的情况,Servlet规范中还引入了一种补充的
会话管理机制,它允许不支持Cookie的浏览器也可以与Web服务器保持
连续的会话。
当用户单击响应消息中的超链接来发出下一次请求时,如果请求消息中
没有包含Cookie头字段,Servlet引擎则认为客户端不支持Cookie,
它将依据请求URL的参数中的会话标识号来实施会话跟踪。这种方式
要求在响应消息中出现的指向当前应用程序中的资源的超链接地址后面
都附带会话标识号参数,且用户必须通过超链接来提交后续的访问请求。
将会话标识号以参数形式附加在超链接的URL地址后面的技术称为URL重写。
如果浏览器不支持Cookie或者关闭了Cookie功能的情况下,Web服务器
还要能够与浏览器实现有状态的会话,就必须对所有可能被客户端
访问的请求路径进行URL重写。HttpServletResponse接口中定义了
两个用于完成URL重写的方法:
encodeURL方法 用于对超链接和form表单的action属性中设置的
URL进行重写。
encodeRedirectURL方法 用于对要传递给
HttpServletResponse.sendRedirect方法的URL进行重写。
五、Session的典型案例
- 使用Session实现购物车
使用Session技术是处理购物车这类应用的最佳方案,Web服务器在验证每个用户
登录后都会为它分配一个购物车(Session对象),用户以后在其他页面
选择的商品都会加入到它们各自的购物车中,购物结算页面则是取出当前
用户自己的购物车中的所有商品来进行计算的。 - 利用Session防止表单重复提交
通过服务器端程序防止表单重复提交的基本原理如下:
(1)包含有FORM表单的页面必须通过一个服务器程序动态产生,
服务器程序为每次产生的页面中的FORM表单都分配一个唯一的随机标识号,
并在FORM表单的一个隐藏字段中设置这个标识号,
同时在当前用户的Session域中保存这个标识号;
(2)当用户提交FORM表单时,负责接收这一请求的服务器程序比较FORM表单
隐藏字段中的标识号与存储在当前用户的Session域中的标识号是否相同,
如果相同则处理表单数据,处理完后清除当前用户的Session域中存储的
标识号。经过这样的处理后,当用户重复提交原来得到的FORM表单时,
当前用户的Session域中已不存在相应的表单标识号。
(3)浏览器只有重新向Web服务器请求包含FORM表单的页面时,服务器程序
才会产生另外一个随机标识号,并将这个标识号保存在Session域中和
作为新返回的FORM表单中的隐藏字段值。 - 利用Session实现一致性验证码
(1)服务器程序为每次产生的页面中的FORM表单都分配一个验证码,
同时在当前用户的Session域中保存这个验证码,
并要求用户将提示的验证码手工填写进一个表单字段中;
(2)服务器程序接收到表单数据后,首先判断用户是否填写了正确的验证码,
只有填写的验证码与服务器端保存的验证码匹配时,
服务器程序才开始正常的表单处理流程。
(3)验证码使用一次即失效,用户只能重新向服务器发出访问表单填写页面的
请求来获得新的验证码,并填写新的验证码后才能再次提交有效的表单请求,
这样将大大增加了用户重复操作的难度。 - 跟踪用户上次访问站点的时间及Cookie中文问题
1、跟踪用户上次访问站点的时间
实现原理就是将每一个会话作为一次访问过程,
将每次会话的开始时间作为每次访问网站的时间,
然后将这个时间以Cookie的形式存储到客户端的计算机中,
客户端进行下次访问时通过该Cookie回传上次访问站点的时间值。
2、Cookie中文问题
采用BASE64编码方式将Cookie值编码成了可打印的ASCII字符后再进行传递
编码:
BASE64Encoder encoder = new sun.misc.BASE64Encoder();
String base64CreateTime = encoder.encode(
formatedCreateTime.getBytes("GB2312"));
解码:
BASE64Decoder decoder = new sun.misc.BASE64Decoder();
String decodedAccessTime = new String(
decoder.decodeBuffer(lastAccessTime), "GB2312");
六、Session的持久化管理
- Session的持久化
为了提高服务器内存资源的利用率,Web服务器通常将那些暂时不活动但未超时的
HttpSession对象转移到文件系统或数据库中保存,一旦服务器需要使用它们时,
再将它们从文件系统或数据库中装载进内存,这种技术称为Session的持久化。
存储在HttpSession对象中的每个属性对象必须是可序列化的,
即必须是实现了Serializable接口的对象。
Session持久化具有如下一些作用:
(1)可以提高服务器内存资源的利用率。
(2)在多台Web服务器协同对外提供服务的集群系统中,借助Session的持久化
技术,某台服务器可以将其中发生了改变的Session对象复制给其他服务器。
(3)在一个Web应用程序关闭后接着重新启动时,服务器也会持久化
该应用程序内存中的所有HttpSession对象。通过这种方式,
可以保障重启服务器和应用程序时,一些客户端的会话活动仍可继续。 - Tomcat中的Session持久化管理
Tomcat使用Session Manager类来管理Session的持久化。
Tomcat提供了两个Session Manager类:
org.apache.catalina.session.StandardManager
org.apache.catalina.session.PersistentManager
StandardManager在Web应用程序关闭时,对内存中的所有HttpSession对象
进行持久化,把它们保存到文件系统中,默认的存储文件为:
<tomcat安装目录>\work\Catalina\<主机名>\<应用程序名>\SESSIONS.ser
PersistentManager提供了比StandardManager更为灵活的Session管理,
只要某个设备提供了实现org.apache.catalina.Store接口的驱动类,
PersistentManager就可以将HttpSession对象保存到该设备。Tomcat
提供了将HttpSession对象保存到文件系统和数据库的Store接口实现类。
Tomcat默认使用StandardManager,如果要使用其他的Session Mannager,则要在
server.xml配置文件中的Context元素下增加一个名为Manager的子元素,如下:
<Context path = "/xxx" docBase="xxx">
<Manager className="org.apache.catalina.session.PersistentManager" ...>
<Store className="..." ...>
</Manager>
</Context>