会话管理
实验预习内容
- 什么是会话,一个会话的生产周期从什么时候,到什么时候结束?
答:会话(session)是客户与服务器之间的不间断的请求响应序列。当一个客户向服务器发送第一个请求时就开始了一个会话。对该客户之后的每个请求,服务器能够识别出请求来自于同一个客户。当客户明确结束会话或服务器在一个预定义的时限内没从客户接收任何请求时,会话就结束了。当会话结束后,服务器就忘记了客户以及客户的请求。 - 服务器是如何识别管理属于某一个特定客户的会话的?
答:(1)当客户向服务器发送第一个请求时,服务器就可以为该客户创建一个HttpSession会话对象,并将请求对象与该会话对象关联。服务器在创建会话对象时为其指定一个唯一标识符,称为会话ID,它可以作为该客户的唯一标识。此时,该会话处于新建状态,可以使用HttpSession接口的isNew()来确定会话是否处于该状态。(2)当服务器向客户发送响应时,服务器将该会话ID与响应数据一起发送给客户,这是通过Set-Cookie响应头实现的。(3)客户在接收到响应后将会话ID存储在浏览器的内存中。当客户再次向服务器发送请求时,它使用Cookie请求头把会话ID与请求一起发送给服务器。(4)服务器接收到请求后,从请求对象中取出会话ID,在服务器中查找之前创建的会话对象,找到后将该请求与之前创建的ID值相同的会话对象关联起来。 - 什么是Cookie,它的作用是什么?Cookie会给客户端带来安全隐患吗?
答:(1)Cookie是客户访问Web服务器时,服务器在客户硬盘上存放的信息,它实际上是一小段文本信息,客户以后访问同一个Web服务器时浏览器会把它们原样发送给服务器。通过让服务器读取它原先保存到客户端的信息,网站能够为浏览者提供一系列的方便。(2)Cookie不会给客户端带来安全隐患,它不会以任何方式执行。 - 如何使用隐藏表单域传递会话信息,一般用在什么情况下?
答:当表单提交时,浏览器将指定的名称和值包含在GET或POST的数据中。这个隐藏域可以存储有关会话的信息。但它的缺点是:仅当每个页面都是由表单提交而动态生成时,才能使用这种方法。单击常规的超链接并不产生表单提交,因此,隐藏的表单域不能支持通常的会话跟踪,只能用在某些特定的操作中,例如在线商店的结账过程。
实验内容与步骤
- 使用HttpSession对象管理会话。在名为exp04的Web项目下,创建一个名为ShowSessionInfo的Servlet,显示当前客户的会话ID、会话创建时间、最近一次访问会话的时间、该客户访问会话次数等信息,运行的结果要求如下图所示。
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.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(name = "ShowSessionInfo",value = "/Show")
public class ShowSessionInfo extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=gb2312");
HttpSession session = request.getSession(true);
String heading = null;
String info="Information about your session";
Integer accessCount = (Integer) session.getAttribute("accessCount");
if(accessCount==null)
{
accessCount = new Integer(1);
heading = "Welcome,enter this page first time! ";
}
else
{
heading = "Welcome Back! ";
accessCount = accessCount+1;
}
session.setAttribute("accessCount",accessCount);
PrintWriter out = response.getWriter();
out.println("<HTML>");
out.println(" <BODY><center>");
out.println("<h2>"+heading + "</h2>" +"<h3>" +info+"</h3>");
out.println("<table border='0'>");
out.println("<tr bgcolor=\"ffad0\"><td><b>Info Type</b><td><b>Value</b>\n");
out.println("<tr><td>ID:<td>"+session.getId()+"\n");
out.println("<tr><td>Creation Time:<td>");
out.println(""+new Date(session.getCreationTime())+"\n");
out.println("<tr><td>Time of last access:<td>");
out.println(""+new Date(session.getLastAccessedTime())+"\n");
out.println("<tr><td>Access number:<td>"+accessCount+"\n");
out.println("</table>");
out.println(" </center> </BODY>");
out.println("</HTML>");
}
}
运行截图:
2. 使用HttpSession会话对象设计一个GuessNumberServlet.java,实现简单的猜数游戏:
doget()方法显示当前会话的相关信息,产生一个1-100的随机数并保存到session作用域中,显示表单让用户输入所猜数字,表单以post方式提交给该servlet本身进行处理。
dopost()方法中将用户输入的数字和session中保存的随机数进行比较,如果用户猜的结果正确,强制结束会话,通过超链接可以在此请求该Servlet重新开始一轮猜数游戏;如果结果错误,显示错误提示信息和猜数表单,允许用户重新猜数。
package com.example.exp04;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.WebServlet;
@WebServlet(name = "GuessNumberServlet",value = "/GuessNumberServlet")
public class GuessNumberServlet extends HttpServlet{
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
int magic = (int)(Math.random()*101);
//System.out.print("magic="+magic);
HttpSession session = request.getSession();
// 将随机生成的数存储到会话对象中
session.setAttribute("num",new Integer(magic));
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("我想出一个0到100之间的数,请你猜!");
out.println("<form action='GuessNumberServlet' method='post'>");
out.println("<input type='text' name='guess' />");
out.println("<input type='submit' value='确定'/>");
out.println("</form>");
out.println("</body></html>");
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// 得到用户猜的数
int guess = Integer.parseInt(request.getParameter("guess"));
HttpSession session = request.getSession();
// 从会话对象中取出随机生成的数
int magic = (Integer)session.getAttribute("num");
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.println("<html><body>");
if(guess==magic){
session.invalidate(); // 销毁会话对象
out.println("祝贺你,答对了!");
out.println("<a href = 'GuessNumberServlet'>再猜一次.</a>");
}else if(guess>magic){
out.println("太大了! 请重猜!");
}else{
out.println("太小了! 请重猜!");
}
out.println("<form action='GuessNumberServlet' method='post'>");
out.println("<input type='text' name='guess' />");
out.println("<input type='submit' value='确定'/>");
out.println("</form>");
out.println("</body></html>");
}
}
运行截图:
3. 编写一个CheckUserServlet,通过Cookie实现自动登录的功能。当用户以get方式请求该Servlet时,判断来自请求的cookie中是否包含用户的登录名和口令,如果有判断是否合法,如果通过验证显示欢迎信息;否则显示登录表单让用户重新填写用户名和口令,表单提交以post方式请求CheckUserServlet进行处理,如果用户登录成功并且勾选了“自动登录”,则提示登录成功,并向客户端发送cookie信息保存用户名和口令,否则提示登录失败,并在浏览器端显示登录表单让用户重新登录。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>login</title>
</head>
<body>
${sessionScope.message}<br>
<form action="C heckUserServlet" method="post">
请输入用户名和口令:<br>
用户名:<input type="text" name="username"/><br>
口令:<input type="password" name="password"/><br>
<input type="checkbox" name="check" value="check"/>自动登录<br>
<input type="submit" value="提交"/>
<input type="reset" value="重置"/>
</form>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>welcome</title>
</head>
<body>
<h1>欢迎你</h1>
</body>
</html>
package com.example.exp04;
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;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Locale;
@WebServlet("/CheckUserServlet")
public class CheckUserServlet extends HttpServlet {
String message =null;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
String value1 = "",value2="";
Cookie cookie =null;
Cookie[] cookies = request.getCookies();
if(cookies!=null){
for(int i=0;i<cookies.length;i++){
cookie = cookies[i];
if(cookie.getName().equals("username")){
value1 = cookie.getValue();
}
if(cookie.getName().equals("password")){
value2 = cookie.getValue();
}
}
if (value1.equals("admin")&&value2.equals("admin")){
message = "欢迎您!"+value1+"再次登录该页面!";
request.getSession().setAttribute("message",message);
response.sendRedirect("welcome.jsp");
}else {
response.sendRedirect("login.jsp");
}
}else {
response.sendRedirect("login.jsp");
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
String username = request.getParameter("username").trim();
String password = request.getParameter("password").trim();
if (!username.equals("admin")||!password.equals("admin")){
message = "用户名或口令不正确,请重试!";
request.getSession().setAttribute("message",message);
response.sendRedirect("login.jsp");
}else {
//如果用户选中了“自动登录”复选框,向浏览器发送连个Cookie
if ((request.getParameter("check")!=null) && (request.getParameter("check").equals("check"))){
Cookie nameCookie = new Cookie("username",username);
Cookie pswdCookie = new Cookie("password",password);
nameCookie.setMaxAge(60*60);
pswdCookie.setMaxAge(60*60);
response.addCookie(nameCookie);
response.addCookie(pswdCookie);
}
message ="你已成功登录!";
request.getSession().setAttribute("message",message);
response.sendRedirect("welcome.jsp");
}
}
}
4. 编写HomeServlet.java,对通过超链接请求的两个URL进行重写,在浏览器中禁用Cookie后,servlet运行效果要求如下图所示。
package com.example.exp04;
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 java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/HomeServlet")
public class HomeServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
//HttpSession session=request.getSession();
PrintWriter out = response.getWriter();
String url1 = response.encodeURL("GuessNumberServlet");
String url2 = response.encodeURL("CheckUserServlet");
out.println("<HTML>");
out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>");
out.println(" <BODY>");
out.print(" A test page showing two URLs:<br> ");
out.println("<a href=\"" + url1 + "\">View GuessNumber Servlet</a><br>");
out.println("<a href=\""+ url2 + "\">View CheckUser Servlet</a><br>");
out.println(" </BODY>");
out.println("</HTML>");
}
}
思考题
- 如何理解会话失效与超时?如何通过程序设置最大失效时间?如何通过Web应用程序部署描述文件设置最大超时时间?二者有什么区别?
答:当用户在一个指定的期限内处于不活动状态时,就将用户的会话终止,会话失效,超过设定时间终止是会话超时。public void setMaxInactiveInterval(int Interval)设置最大失效时间。在部署文件中下中设置最大超时时间。一个是通过编程方式设置,一个是在部署时就已设置。 - 能否通过客户机的IP地址实现会话跟踪?
答:容器不能使用客户的IP地址唯一标识客户。因为是通过局域网访问Internet尽管在局域网中每个客户有一个IP地址,但对于服务器来说,客户的实际IP地址是路由器的IP地址,所以该局域网的所有客户的IP地址都相同。 - 假如开发的Web应用程序是假设客户支持Cookie的,但应用程序部署后,你发现大多数客户禁用了Cookie,这对应用程序有何影响?如何修改它?
答:来自网站的所有Cookie都被阻止,并且计算机上现有的Cookie不能被网站读取。可在Internet选项中设置。