1. 会话管理的概念:
1) 背景:HTTP协议中,服务器无记忆,每次请求都是新的请求,是一种无状态通信协议,但是有些需求要求能记得前后请求之间的关系,比如用户在多个网页之间采购商品、网络笔试等;
2) 会话管理:管理多次请求之间关系的技术,要求保存前后请求响应的一些中间结果;
3) 目前会话管理的4中常用方法:隐藏域、Cookie、URL重写、使用HttpSession类,这4中方法各有特色,各有各的应用场合,而HttpSession类最完备,功能最完全;
2. 使用隐藏域记录之前请求响应的信息:
1) 隐藏域是HTML的一种input表单,其属性type为"hidden",其余属性和普通的input相同,有参数名和参数值,例如:<input type="hidden" name="var" value="123">;
2) 隐藏域回合其它参数一样被提交给服务器,比如上面的那个例子就是?var=123,但和其它参数不同的是隐藏域不会显示在HTML页面中,而其它参数会以各种形式予以显示,比如按钮、列表等等,因此叫做隐藏域;
3) 使用隐藏域进行会话管理的思想是:
i. 在Servlet中将上一次请求的响应结果用Response写成隐藏域返回给浏览器;
ii. 那么上一次响应结果会以隐藏域的形式隐藏在响应页面中;
iii. 然后用户操作完响应页面再提交,那么上一次的结果就会通过隐藏域的形式上传给服务器,那么这样服务器就能再次获得上一次请求的响应结果了!
4) 程序示例:一个网页调查问卷,题目分页作答,打完第一页按“下一页”按钮后才会显示第二页的题目
package com.lirx; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class HiddenField */ @WebServlet("/questionnaire") public class HiddenField extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public HiddenField() { super(); // TODO Auto-generated constructor stub } public void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setContentType("text/html; charset=UTF-8"); PrintWriter out = response.getWriter(); out.println("<html>"); out.println(" <head>"); out.println(" <title>Questionnaire</title>"); out.println(" </head>"); out.println(" <body>"); out.println(" <form action='questionnaire' method='post'>"); String page = request.getParameter("page"); if (page == null) { out.println("问题一:<input type='text' name='p1q1'><br>"); out.println("问题二:<input type='text' name='p1q2'><br>"); out.println("<input type='submit' name='page' value='下一页'>"); } else if ("下一页".equals(page)) { String p1q1 = request.getParameter("p1q1"); String p1q2 = request.getParameter("p1q2"); out.println("<input type='hidden' name='p1q1' value='" + p1q1 + "'>"); out.println("<input type='hidden' name='p1q2' value='" + p1q2 + "'>"); out.println("问题三:<input type='text' name='p2q1'><br>"); out.println("<input type='submit' name='page' value='完成'>"); } else if ("完成".equals(page)) { out.println("您的答案是:<br>"); out.println(request.getParameter("p1q1") + "<br>"); out.println(request.getParameter("p1q2") + "<br>"); out.println(request.getParameter("p2q1") + "<br>"); } out.println(" </form>"); out.println(" </body>"); out.println("</html>"); } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse * response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub //response.getWriter().append("Served at: ").append(request.getContextPath()); processRequest(request, response); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse * response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub //doGet(request, response); processRequest(request, response); } }
!!!隐藏域安全性很差,上次的结果可以通过查看网页源码的方式看到,不适用于隐秘性较高的数据!
3. Cookie:
1) Cookie是浏览器存储服务器发来的信息的一种方式,服务器发给浏览器的信息可以以Cookie为单位存储在浏览器中,等到下次请求时可以把保存的Cookie再发送给服务器进行处理;
2) Cookie的格式和标头:
i. 一个cookie表示一个键值对,即参数和参数值(都是字符串);
ii. 在服务器和浏览器之间传送Cookie时将cookie保存在set-cookie标头内(请求标头和响应标头),例如:
set-cookie:user=Peter;number=12345 // 这里就表示传送了两个cookie,一个是user=Peter,另一个是number=12345,多个cookie都写在一个set-cookie标头下
3) Cookie的保存方式:Cookie将以文件的形式保存在浏览器所在的计算机上,等到下次请求同样的网站时会将该网页所属域的所有Cookie都发送给服务器;
4) Servlet创建与获取Cookie的API:
i. 创建、设置生命周期、添加Cookie:
a. 创建:Cookie(String name, String value); // 键值对为参数
b. 设置生命周期:void Cookie.setMaxAge(int expiry); // 设置本Cookie最多在浏览器端存活几秒,超时就会清除
c. 添加:void HttpServletResponse.addCookie(Cookie cookie); // 将一个Cookie添加到set-cookie响应标头中,可以添加多个,第一次添加意味着会在标头中加入set-cookie标头
!!既然Cookie是通过标头传送的,因此添加Cookie必须要在响应确认之前添加,否则无效;
ii. 获取浏览器发送来的Cookie:
a. 获取:Cookie[] HttpServletRequest.getCookies(); // 返回请求标头中所有的Cookie
b. 对Cookie进行解析,获取参数名:String Cookie.getName();
c. 获取参数值:String Cookie.getValue();
d. 举例:
Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie: cookies) { String name = cookie.getName(); String value = cookie.getValue(); ... } }
4. 自动登录功能——Cookie的常见应用:
1) 首先是网站首页/index.do:首页Servlet会检查用户是否有访问过该网站的Cookie历史,如果有则提取Cookie中以前的登录信息尝试登录,如果没有Cookie历史则表示没有登陆过,因此会跳转到登录页面/login.do:即如果有自动登录历史则会跳过login Servlet直接进入欢迎页面
package com.lirx; import java.io.IOException; 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; /** * Servlet implementation class Index */ @WebServlet("/index.do") public class Index extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public Index() { super(); // TODO Auto-generated constructor stub } protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie: cookies) { // 这里设定只有登陆过并且选择自动登录复选框后只将登录账户保存至Cookie // 密码就不保存了,下次就直接凭账户就能登录,密码保存至Cookie容易被解析 String name = cookie.getName(); String value = cookie.getValue(); // 有Cookie信息就要解析是否是登录账号 if ("user".equals(name) && "Peter".equals(value)) { // 这里设定只能有一个账户user=Peter // 由于是自动登录并且保存过信息,因此无需密码直接登录 request.setAttribute(name, value); // 用对象属性包装并转发给视图来显示 // 不要直接将登录信息作为GET请求参数,一般账户信息都需要隐藏起来 // 不要直接以"/user.view?${name}=${value}"的形式转发 request.getRequestDispatcher("/user.view").forward(request, response); return ; } } } response.sendRedirect("login.html"); // 如果没Cookie信息则转到登录页面 } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse * response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub //response.getWriter().append("Served at: ").append(request.getContextPath()); processRequest(request, response); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse * response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub //doGet(request, response); processRequest(request, response); } }2) 登录页面:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>登录网页</title> </head> <body> <form action="login.do" method="post"> 名称:<input type="text" name="user"><br> 密码:<input type="password" name="passwd"><br> 自动登录:<input type="checkbox" name="login" value="auto"><br> <input type="submit" value="发送"> </form> </body> </html>3) 验证登录信息的Servlet,账户、密码通过login.html的表单提交,还要在其中保存“自动登录”的Cookie:
package com.lirx; import java.io.IOException; 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; /** * Servlet implementation class Login */ @WebServlet("/login.do") public class Login extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public Login() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub response.getWriter().append("Served at: ").append(request.getContextPath()); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ // 有login.html页面通过表单的形式将账户、密码提交给该Servlet // 由于包含密码等敏感信息,所以必须以POST的方式请求 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub //doGet(request, response); String user = request.getParameter("user"); String passwd = request.getParameter("passwd"); if ("Peter".equals(user) && "12345".equals(passwd)) { // 这里设定成只允许user=Peter/12345的用户登录 String login = request.getParameter("login"); if ("auto".equals(login)) { // 如果"login"复选框的值是auto则添加Cookie信息 Cookie cookie = new Cookie("user", "Peter"); cookie.setMaxAge(7 * 24 * 60 * 60); // Cookie生命周期设为7天 response.addCookie(cookie); } request.setAttribute("user", user); // 顺便登录成功显示信息 request.getRequestDispatcher("user.view").forward(request, response); } else { // 非指定账户、密码错误、没填信息等都重新登录,重新返回登录页面 response.sendRedirect("login.html"); } } }4) 显示欢迎画面的Servlet:
package com.lirx; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class User */ @WebServlet("/user.view") public class User extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public User() { super(); // TODO Auto-generated constructor stub } private String htmlTemplate = "<html>" + " <head>" + " <title>Servlet User</title>" + " </head>" + " <body>" + " <h1>%s已登录</h1>" + " </body>" + "</html>"; protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); String html = String.format(htmlTemplate, request.getAttribute("user")); out.println(html); out.close(); } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse * response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub //response.getWriter().append("Served at: ").append(request.getContextPath()); processRequest(request, response); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse * response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub //doGet(request, response); processRequest(request, response); } }
5) 本示例是符合Model 2结构的;
5. URL重写:
1) 就是Servlet直接响应几条<a>超链接给浏览器,超链接里面有URL,而Servlet就将上一次响应结果保存在超链接URL的请求参数中,用户点击超链接后就会通过将上次响应结果通过超链接的请求参数再发送给Servlet,这样就能记住上次访问的信息了;
2) 由于<a>超链接只能以GET方法请求,因此URL重写就是一种GET请求参数的应用;
3) URL重写的典型应用——显示搜索结果:
i. 搜索结果往往是一个很长的列表,一般一个页面放不下,要分页显示;
ii. 一般搜索页面最低端是结果页面的页码,比如1 2 3 4...等,1就表示第一页结果,2就表示第2页结果,它们其实是一个超链接,点开后会打开该页的搜索结果分页;
iii. 该超链接的URL是通过Servlet的URL重写返回的,其参数page的值就是页码;
iv. 示例:
package com.lirx; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class Search */ @WebServlet("/search.view") public class Search extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public Search() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ private String htmlTemplate = "<html>" + " <head>" + " <title>搜索结果</title>" + " </head>" + " <body>" + " 第%d到%d搜索结果:<br>" + " <ul>%s</ul>" + " %s" + " </body>" + "</html>"; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub //response.getWriter().append("Served at: ").append(request.getContextPath()); response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); String page = request.getParameter("page"); if (page == null) page = "1"; int nPage = Integer.parseInt(page) - 1; int begin = nPage * 10 + 1; int end = begin + 9; String searchList = ""; for (int i = 1; i <= 10; i++) searchList += "<li>搜索结果" + i + "</li>"; String pageList = ""; for (int i = 1; i <= 10; i++) { if (i == nPage + 1) { // 当前页面的页码无超链接 pageList += ("" + i + "\n"); continue; } pageList += "<a href='search.view?page=" + i + "'>" + i + "</a>\n"; } String html = String.format(htmlTemplate, begin, end, searchList, pageList); out.println(html); out.close(); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub doGet(request, response); } }4) URL重写的应用场合:
i. 由于URL重写是使用HTML超链接的技术,而超链接必须以GET方式请求;
ii. GET的请求参数长度有限,因此URL重写只能保存信息量很少的中间结果,不适用于大量数据的保存;
iii. URL重写还可以辅助HttpSession会话管理;