首页 > 开发 > 综合 > 正文

会话状态

2024-07-21 02:14:23
字体:
来源:转载
供稿:网友

10.1 会话状态概述

   http协议的“无状态”(stateless)特点带来了一系列的问题。特别是通过在线商店购物时,服务器不能顺利地记住以前的事务就成了严重的问题。它使得“购物篮”之类的应用很难实现:当我们把商品加入购物篮时,服务器如何才能知道篮子里原先有些什么?即使服务器保存了上下文信息,我们仍旧会在电子商务应用中遇到问题。例如,当用户从选择商品的页面(由普通的服务器提供)转到输入信用卡号和送达地址的页面(由支持ssl的安全服务器提供),服务器如何才能记住用户买了些什么?

   这个问题一般有三种解决方法:

cookie。利用http cookie来存储有关购物会话的信息,后继的各个连接可以查看当前会话,然后从服务器的某些地方提取有关该会话的完整信息。这是一种优秀的,也是应用最广泛的方法。然而,即使servlet提供了一个高级的、使用方便的cookie接口,仍旧有一些繁琐的细节问题需要处理:
从其他cookie中分别出保存会话标识的cookie。
为cookie设置合适的作废时间(例如,中断时间超过24小时的会话一般应重置)。
把会话标识和服务器端相应的信息关联起来。(实际保存的信息可能要远远超过保存到cookie的信息,而且象信用卡号等敏感信息永远不应该用cookie来保存。)
改写url。你可以把一些标识会话的数据附加到每个url的后面,服务器能够把该会话标识和它所保存的会话数据关联起来。这也是一个很好的方法,而且还有当浏览器不支持cookie或用户已经禁用cookie的情况下也有效这一优点。然而,大部分使用cookie时所面临的问题同样存在,即服务器端的程序要进行许多简单但单调冗长的处理。另外,还必须十分小心地保证每个url后面都附加了必要的信息(包括非直接的,如通过location给出的重定向url)。如果用户结束会话之后又通过书签返回,则会话信息会丢失。
隐藏表单域。html表单中可以包含下面这样的输入域:<input type="hidden" name="session" value="...">。这意味着,当表单被提交时,隐藏域的名字和数据也被包含到get或post数据里,我们可以利用这一机制来维持会话信息。然而,这种方法有一个很大的缺点,它要求所有页面都是动态生成的,因为整个问题的核心就是每个会话都要有一个唯一标识符。
   servlet为我们提供了一种与众不同的方案:httpsession api。httpsession api是一个基于cookie或者url改写机制的高级会话状态跟踪接口:如果浏览器支持cookie,则使用cookie;如果浏览器不支持cookie或者cookie功能被关闭,则自动使用url改写方法。servlet开发者无需关心细节问题,也无需直接处理cookie或附加到url后面的信息,api自动为servlet开发者提供一个可以方便地存储会话信息的地方。

   10.2 会话状态跟踪api

   在servlet中使用会话信息是相当简单的,主要的操作包括:查看和当前请求关联的会话对象,必要的时候创建新的会话对象,查看与某个会话相关的信息,在会话对象中保存信息,以及会话完成或中止时释放会话对象。

   10.2.1 查看当前请求的会话对象

   查看当前请求的会话对象通过调用httpservletrequest的getsession方法实现。如果getsession方法返回null,你可以创建一个新的会话对象。但更经常地,我们通过指定参数使得不存在现成的会话时自动创建一个会话对象,即指定getsession的参数为true。因此,访问当前请求会话对象的第一个步骤通常如下所示:
httpsession session = request.getsession(true);



   10.2.2 查看和会话有关的信息

   httpsession对象生存在服务器上,通过cookie或者url这类后台机制自动关联到请求的发送者。会话对象提供一个内建的数据结构,在这个结构中可以保存任意数量的键-值对。在2.1或者更早版本的servlet api中,查看以前保存的数据使用的是getvalue("key")方法。getvalue返回object,因此你必须把它转换成更加具体的数据类型。如果参数中指定的键不存在,getvalue返回null。

   api 2.2版推荐用getattribute来代替getvalue,这不仅是因为getattribute和setattribute的名字更加匹配(和getvalue匹配的是putvalue,而不是setvalue),同时也因为setattribute允许使用一个附属的httpsessionbindinglistener 来监视数值,而putvalue则不能。

   但是,由于目前还只有很少的商业servlet引擎支持2.2,下面的例子中我们仍旧使用getvalue。这是一个很典型的例子,假定shoppingcart是一个保存已购买商品信息的类:
httpsession session = request.getsession(true);
shoppingcart previousitems =
(shoppingcart)session.getvalue("previousitems");
if (previousitems != null) {
dosomethingwith(previousitems);
} else {
previousitems = new shoppingcart(...);
dosomethingelsewith(previousitems);
}



   大多数时候我们都是根据特定的名字寻找与它关联的值,但也可以调用getvaluenames得到所有属性的名字。getvaluesnames返回的是一个string数组。api 2.2版推荐使用getattributenames,这不仅是因为其名字更好,而且因为它返回的是一个enumeration,和其他方法(比如httpservletrequest的getheaders和getparameternames)更加一致。

   虽然开发者最为关心的往往是保存到会话对象的数据,但还有其他一些信息有时也很有用。

getid:该方法返回会话的唯一标识。有时该标识被作为键-值对中的键使用,比如会话中只保存一个值时,或保存上一次会话信息时。
isnew:如果客户(浏览器)还没有绑定到会话则返回true,通常意味着该会话刚刚创建,而不是引用自客户端的请求。对于早就存在的会话,返回值为false。
getcreationtime:该方法返回建立会话的以毫秒计的时间,从1970.01.01(gmt)算起。要得到用于打印输出的时间值,可以把该值传递给date构造函数,或者gregoriancalendar的settimeinmillis方法。
getlastaccessedtime:该方法返回客户最后一次发送请求的以毫秒计的时间,从1970.01.01(gmt)算起。
getmaxinactiveinterval:返回以秒计的最大时间间隔,如果客户请求之间的间隔不超过该值,servlet引擎将保持会话有效。负数表示会话永远不会超时。
   10.2.3 在会话对象中保存数据

   如上节所述,读取保存在会话中的信息使用的是getvalue方法(或,对于2.2版的servlet规范,使用getattribute)。保存数据使用putvalue(或setattribute)方法,并指定键和相应的值。注意putvalue将替换任何已有的值。有时候这正是我们所需要的(如下例中的referringpage),但有时我们却需要提取原来的值并扩充它(如下例previousitems)。示例代码如下:
httpsession session = request.getsession(true);
session.putvalue("referringpage", request.getheader("referer"));
shoppingcart previousitems =
(shoppingcart)session.getvalue("previousitems");
if (previousitems == null) {
previousitems = new shoppingcart(...);
}
string itemid = request.getparameter("itemid");
previousitems.addentry(catalog.getentry(itemid));

session.putvalue("previousitems", previousitems);



   10.3 实例:显示会话信息

   下面这个例子生成一个web页面,并在该页面中显示有关当前会话的信息。
package hall;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.*;
import java.util.*;

public class showsession extends httpservlet {
public void doget(httpservletrequest request,
httpservletresponse response)
throws servletexception, ioexception {
httpsession session = request.getsession(true);
response.setcontenttype("text/html");
printwriter out = response.getwriter();
string title = "searching the web";
string heading;
integer accesscount = new integer(0);;
if (session.isnew()) {
heading = "welcome, newcomer";
} else {
heading = "welcome back";
integer oldaccesscount =
// 在servlet api 2.2中使用getattribute而不是getvalue
(integer)session.getvalue("accesscount");
if (oldaccesscount != null) {
accesscount =
new integer(oldaccesscount.intvalue() + 1);
}
}
// 在servlet api 2.2中使用putattribute
session.putvalue("accesscount", accesscount);

out.println(servletutilities.headwithtitle(title) +
"<body bgcolor=\"#fdf5e6\">\n" +
"<h1 align=\"center\">" + heading + "</h1>\n" +
"<h2>information on your session:</h2>\n" +
"<table border=1 align=center>\n" +
"<tr bgcolor=\"#ffad00\">\n" +
" <th>info type<th>value\n" +
"<tr>\n" +
" <td>id\n" +
" <td>" + session.getid() + "\n" +
"<tr>\n" +
" <td>creation time\n" +
" <td>" + new date(session.getcreationtime()) + "\n" +
"<tr>\n" +
" <td>time of last access\n" +
" <td>" + new date(session.getlastaccessedtime()) + "\n" +
"<tr>\n" +
" <td>number of previous accesses\n" +
" <td>" + accesscount + "\n" +
"</table>\n" +
"</body></html>");
}
public void dopost(httpservletrequest request,
httpservletresponse response)
throws servletexception, ioexception {
doget(request, response);
}
}



发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表