1. Cookie
1.1. Cookie概述
Cookie译为小型文本文件或小甜饼,Web应用程序利用Cookie在客户端缓存服务器端文件。Cookie是以键值对形式存储在客户端主机硬盘中,由服务器端发送给客户端,客户端再下一次访问服务器端时,服务器端可以获取到客户端Cookie缓存文件。
Cookie是由服务器端创建的,然后由服务器端发送给客户端,客户端以键值对形式存储Cookie,并标注Cookie的来源。客户端再次访问服务器端时,存储的Cookie会保存在请求协议中,服务器端可以获取上次存储的缓存文件内容。
Cookie的用途:
- 管理浏览网站的人数(其中包含有多少人访问过,多少人是新用户等)
- 电子商城中购物车功能(每买一样商品,保存一个Cookie)
- 用户自动登录功能(第一次登录时,将用户名和密码存储在Cookie)
Cookie的缺点:
- 多人共用一台计算机(例如导致用户名和密码不安全等问题)。
- Cookie被删除时,利用Cookie统计用户数量出现偏差。
- 一人使用多台计算机(网站会将看成多个用户等问题)
- Cookie会被附加在每次Http请求协议中,增加流量。
- Cookie使用明文(未加密)传递的,安全性低。
- Cookie的大小限制在4KB左右,无法存储复杂需求。
1.2. 百度百科
Cookie中文名称为小型文本文件,指某些网站为了辨别用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。定义于RFC2109。为网景公司的前雇员Lou Montulli在1993年3月所发明。
Cookie一词用在程序设计中是一种能够让网站服务器把少量数据储存到客户端的硬盘或内存,或是从客户端的硬盘读取数据的一种技术。从本质上讲,它可以看作是你的身份证。保存的信息片断以"名/值"对(name-value pairs)的形式储存,一个"名/值"对仅仅是一条命名的数据。
一个网站只能取得它放在你的电脑中的信息,它无法从其它的Cookie文件中取得信息,也无法得到你的电脑上的其它任何东西。 Cookie中的内容大多数经过了加密处理,因此一般用户看来只是一些毫无意义的字母数字组合,只有服务器的CGI处理程序才知道它们真正的含义。
1.3. 维基百科
Cookie(复数形态Cookies),中文名称为小型文本文件或小甜饼,指某些网站为了辨别用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。定义于RFC2109。为网景公司的前雇员Lou Montulli在1993年3月所发明。
Cookie总是保存在客户端中,按在客户端中的存储位置,可分为内存Cookie和硬盘Cookie。
内存Cookie由浏览器维护,保存在内存中,浏览器关闭后就消失了,其存在时间是短暂的。硬盘Cookie保存在硬盘里,有一个过期时间,除非用户手工清理或到了过期时间,硬盘Cookie不会被删除,其存在时间是长期的。所以,按存在时间,可分为非持久Cookie和持久Cookie。
1.4. Cookie规范
Http协议提供了有关Cookie的规范,现今市场上出现大量浏览器,一些浏览器对该Cookie规范进行了一些“扩展”,但Cookie缓存文件不会占满硬盘空间。
- Cookie存储的大小上限为4KB。
- 一个服务器最多在客户端浏览器中可以保存20个Cookie。
- 一个浏览器最多可以保存300个Cookie。
值得注意的是,不同浏览器之间不能共享Cookie缓存文件。例如,上次使用IE浏览器访问网站,下次使用火狐浏览器访问该网站时,不能使用IE浏览器保存的Cookie缓存文件。
1.5. 第一个Cookie
下面我们通过具体操作来讨论一下Cookie在Web应用程序运行中的状态,具体操作如下:
- 创建一个Servlet用于向客户端发送Cookie信息。
public class CookieServlet1 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1 利用UUID生成一个随机字符串 String id = UUID.randomUUID().toString(); //2 创建Cookies实例对象 Cookie cookie = new Cookie("id", id); //3 将Cookies实例对象,添加到Response对象中 response.addCookie(cookie); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 创建另一个Servlet用于获取上次服务器端向客户端发送的Cookie信息。
public class CookieServlet2 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); //1 通过Request对象获取上次服务器端发送的Cookie信息 Cookie[] cookies = request.getCookies(); //2 判断Request对象中的Cookie是否存在 if(cookies != null){ //3 遍历Request对象中的所有Cookie for (Cookie cookie : cookies) { //4 获取每一个Cookie的名称 String name = cookie.getName(); //5 判断Cookie的名称是否存在是id if(name.equals("id")){ //6 打印Cookie名称为id的Cookie值 response.getWriter().println("你的ID是"+cookie.getValue()); } } } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 配置Web工程的web.xml文件。
xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>display-name> <servlet> <servlet-name>CookieServlet1servlet-name> <servlet-class>app.java.cookie.CookieServlet1servlet-class> servlet> <servlet> <servlet-name>CookieServlet2servlet-name> <servlet-class>app.java.cookie.CookieServlet2servlet-class> servlet> <servlet-mapping> <servlet-name>CookieServlet1servlet-name> <url-pattern>/cookie1url-pattern> servlet-mapping> <servlet-mapping> <servlet-name>CookieServlet2servlet-name> <url-pattern>/cookie2url-pattern> servlet-mapping> web-app>
1.6. 显示上次访问时间案例
利用Cookie完成显示上次访问事件案例,首先需要分析一下整个案例的工作流程是怎么样的。
根据分析的工作流程,具体的操作步骤如下:
- 创建一个Servlet用于判断是否存在Cookies,并做出不同响应。
public class CookieServlet3 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); PrintWriter out = response.getWriter(); //1 通过Request对象获取请求中的Cookies Cookie[] cookies = request.getCookies(); //2 判断请求中的Cookies是否存在 if(cookies != null){ //3 遍历所有Cookies for (Cookie cookie : cookies) { //4 获取每一个Cookies的名称 String name = cookie.getName(); //5 判断是否存在一个名为"lastvisit"的Cookie if(name.equals("lastvisit")){ // 如果存在,说明不是第一次访问,显示上次访问的时间. out.println("你上次访问的时间是"+cookie.getValue()+"
"); } } }else{ // 如果不存在,说明是第一次访问,显示欢迎信息. out.println("欢迎访问XXX网站.
"); } // 创建用于显示最后一次访问时间的Cookie实例对象 Cookie cookie = new Cookie("lastvisit", new Date().toString()); // 将Cookie实例对象添加到Response对象中 response.addCookie(cookie); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 配置Web工程的web.xml文件。
xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>display-name> <servlet> <servlet-name>CookieServlet3servlet-name> <servlet-class>app.java.cookie.CookieServlet3servlet-class> servlet> <servlet-mapping> <servlet-name>CookieServlet3servlet-name> <url-pattern>/cookie3url-pattern> servlet-mapping> web-app>
1.7. 会话跟踪技术
在上述案例中,如果关闭浏览器后,再次打开浏览器访问相同地址。会发现页面显示第一次访问的欢迎信息,而不是显示上次访问的时间。
原因在于当关闭浏览器时,一次会话就结束了。再次打开浏览器访问相同地址时,而是开启了又一次的会话。由于Http协议是无状态的(记不住上次会话的状态),所以两次会话之间是相互独立的。
会话(没有官方定义),可以简单理解为用户打开浏览器,访问某个网站,直到关闭浏览器,整个过程称之为一次会话。
所谓协议的无状态是指同一个会话的连续两个请求相互独立。当浏览器发送请求给服务器时,服务器响应,同一个浏览器再次发送请求给服务器时,服务器依旧会响应,但是服务器不会知道是否是同一个浏览器发送的请求。每一次请求和响应都是相对独立的。
这种协议的无状态会话会阻碍实现例如购物车等功能,当用户选择一个商品后,再次选择另一个商品时,服务器端无法知道上一次选择的商品是什么。而为了解决这种协议的无状态会话,出现了Cookie和Session两种技术。
Cookie是通过客户端保持状态的解决方案。从定义上来讲,Cookie就是由服务器端响应给客户端的特殊信息,而这些信息以文本文件的形式存储在客户端,然后每次客户端向服务器发送请求时都会携带这些特殊的信息。
Session是通过服务器端保持状态的解决方案。通常会把Session译为会话,当客户端第一次向服务器端发送请求时,服务器端可以创建Session,Session的相关内容会保存在服务器端,并生成唯一的Session ID。客户端随后的请求都会携带这个Session ID,服务器端根据Session ID找到对应Session,再次使用。
1.8. Cookie常用API
在JavaEE中提供了javax.servlet.http.Cookie类,该类提供了获取Cookie与设置Cookie的相关方法。具体内容如下:
Method Summary |
|
String |
getDomain() |
int |
getMaxAge() |
String |
getName() |
String |
getPath() |
String |
getValue() |
void |
setDomain(String pattern) |
void |
setMaxAge(int expiry) |
void |
setPath(String uri) |
void |
setValue(String newValue) |
Cookie的常用API方法,可以通过几个方面分别学习讨论。
- 向客户端响应Cookie与读取请求中的Cookie
- 向客户端响应Cookies
// 创建Cookie实例对象 Cookie cookie = new Cookie("id", new Date().toString()); // 将Cookie实例对象添加到Response对象中 response.addCookie(cookie);
-
- 读取请求中的Cookie
//1 通过Request对象获取请求中的Cookies Cookie[] cookies = request.getCookies(); //2 判断请求中的Cookies是否存在 if(cookies != null){ //3 遍历所有Cookies for (Cookie cookie : cookies) { return cookie; } }else{ return null; }
读取Cookie时,可以利用getName()方法、getValue()方法获取每一个Cookie中的名称和值,也可以利用setValue()方法重新设置对应名称的Cookie的值。但Cookie的名称只有创建时才能设置,一旦创建就不能更改(Cookie规范中认为Cookie的名称是唯一的)。
if(cookies != null){ // 遍历所有Cookies for (Cookie cookie : cookies) { // 获取每一个Cookies的名称 String name = cookie.getName(); // 判断是否存在一个名为"lastvisit"的Cookie if(name.equals("lastvisit")){ // 如果存在,说明不是第一次访问,显示上次访问的时间. out.println("你上次访问的时间是"+cookie.getValue()+"
"); } } }
- 会话Cookie与持久Cookie
- 会话Cookie
会话Cookie是保存在浏览器的内存中,当一次会话结束或关闭浏览器后,Cookie信息将会丢失或被删除。
-
- 持久Cookie
持久Cookie是保存在浏览器的临时文件夹中,当一次会话结束或关闭浏览器后,Cookie信息依然会被保存。
值得注意的是,持久Cookie存在过期时间,过期后Cookie会被自动删除。
可以利用setMaxAge(int age)方法设置持久Cookie的有效时间。
// 创建Cookie实例对象 Cookie cookie = new Cookie("id", new Date().toString()); // 设置Cookie的有效时间 cookie.setMaxAge(60 * 60); // 将Cookie实例对象添加到Response对象中 response.addCookie(cookie);
- 设置Cookie的有效访问路径
- 设置Cookie的有效访问路径,使用setPath()方法。
- 默认情况下,Cookie的有效访问路径为当前Web应用程序的根目录“/10_cookies”(Web应用程序的内部路径)。
- 默认情况下,生成的持久Cookie文件的名称为有效访问路径。
- 设置setPath(“/”)表示Cookie的有效访问路径为当前Web应用程序下的所有目录。
- 请求资源路径与Cookie的有效访问路径必须一致。
// 创建Cookie实例对象 Cookie cookie = new Cookie("id", new Date().toString()); // 设置Cookie的有效时间 cookie.setMaxAge(60 * 60); // 设置Cookie的有效访问路径 cookie.setPath("/abc"); // 将Cookie实例对象添加到Response对象中 response.addCookie(cookie);
这时生成的Cookie的有效访问路径为“/abc”。
这时再次访问相同资源路径时,由于访问资源路径为http://localhost:8080/cookies,与Cookie的有效访问路径不一致,导致Cookie失效。
- 设置Cookie的有效域名
- 利用setDomain()方法设置Cookies的有效域名,例如setDomain(“.longestory.com”)。
- 大部分的浏览器不支持setDomain()方法,因为可能A网站生成B网站的域名Cookie(第三方Cookie)。
- 删除持久Cookie
- 可以将Cookie的最大有效时间设置为0。注意的是删除Cookie时,有效路径必须一致!
1.9. 商品浏览记录案例
通过Cookie来实现商品浏览记录的案例,首先我们需要分析该案例的实现流程,如下图:
根据分析的实现流程,具体的实现步骤如下:
- 创建一个JSP页面用于显示商品信息及商品浏览记录。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>My JSP 'Show.jsp' starting pagetitle> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> head> <body> <h1>商品列表h1> <a href="/cookies/show?id=1">Java编程思想a><br> <a href="/cookies/show?id=2">Java设计模式a><br> <a href="/cookies/show?id=3">Java语言入门a><br> <a href="/cookies/show?id=4">数据结构和算法a><br> <a href="/cookies/show?id=5">MySQL数据库a><br> <h1>浏览记录h1> <h2><a href="/10_cookies/clear">清空记录a>h2> body> html>
- 创建一个Servlet用于向客户端响应Cookie信息。
public class ShowServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1 通过Request对象获取请求参数ID String id = request.getParameter("id"); //2 通过Request对象获取Cookie信息 Cookie[] cookies = request.getCookies(); //3 判断请求中的Cookies是否存在 if(cookies != null){ //4 遍历所有Cookies for (Cookie cookie : cookies) { //5 获取每一个Cookies的名称 String name = cookie.getName(); //6 判断是否存在一个名为"history"的Cookie if(name.equals("history")){// 存在名为"history"的Cookie // 获取Cookie的value值 String value = cookie.getValue(); // 将Cookie的value值以","切割 String[] ids = value.split(","); // 遍历所有书籍ID for (String eachId : ids) { // 判断当前ID是否已经存在Cookie信息中 if(!eachId.equals(id)){// 表示当前ID不存在Cookie信息中 // 创建用于存储商品浏览记录的Cookie实例对象,将当前书籍ID添加到Cookie信息中 Cookie resCookie = new Cookie("history", value + "," + id); // 设置当前Cookie的生命为1天 resCookie.setMaxAge(60*60*24); // 设置当前Cookie的有效路径为当前Web应用程序下所有目录 resCookie.setPath("/"); // 将当前Cookie添加到Response对象中 response.addCookie(resCookie); } } }else{// 不存在名为"history"的Cookie // 创建用于存储商品浏览记录的Cookie实例对象 Cookie resCookie = new Cookie("history", id); // 设置当前Cookie的生命为1天 resCookie.setMaxAge(60*60*24); // 设置当前Cookie的有效路径为当前Web应用程序下所有目录 resCookie.setPath("/"); // 将当前Cookie添加到Response对象中 response.addCookie(resCookie); } } }else{// 不存在Cookie,表示这是浏览的第一个商品 // 创建用于存储商品浏览记录的Cookie实例对象 Cookie cookie = new Cookie("history", id); // 设置当前Cookie的生命为1天 cookie.setMaxAge(60*60*24); // 设置当前Cookie的有效路径为当前Web应用程序下所有目录 cookie.setPath("/"); // 将当前Cookie添加到Response对象中 response.addCookie(cookie); } response.setContentType("text/html;charset=utf-8"); response.getWriter().println("商品浏览成功!返回 "); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 配置Web工程的web.xml文件。
xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>display-name> <servlet> <servlet-name>ShowServletservlet-name> <servlet-class>app.java.cookie.ShowServletservlet-class> servlet> <servlet-mapping> <servlet-name>ShowServletservlet-name> <url-pattern>/showurl-pattern> servlet-mapping> web-app>
- 发布Web工程到Tomcat服务器,并启动Tomcat服务器。
- 打开浏览器,在地址栏中输入http://localhost:8080/cookies/02_cookie/show.jsp。
- 在商品显示页面,点击浏览某一书籍后,返回商品显示页面。
- 在商品显示页面,再次点击浏览某一书籍后,返回商品显示页面。
- Cookie信息设置成功,下面将浏览的商品信息添加到对应JSP页面中。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>My JSP 'Show.jsp' starting pagetitle> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> head> <body> <h1>商品列表h1> <a href="/10_cookies/show?id=1">Java编程思想a><br> <a href="/10_cookies/show?id=2">Java设计模式a><br> <a href="/10_cookies/show?id=3">Java语言入门a><br> <a href="/10_cookies/show?id=4">数据结构和算法a><br> <a href="/10_cookies/show?id=5">MySQL数据库a><br> <h1>浏览记录h1> <h2><a href="/10_cookies/clear">清空记录a>h2> <% Cookie[] cookies = request.getCookies(); if(cookies!=null){ for (Cookie cookie : cookies) { String name = cookie.getName(); if(name.equals("history")){ String value = cookie.getValue(); String[] ids = value.split(","); String[] names = {"Java编程思想","Java设计模式","Java语言入门","数据结构和算法","MySQL数据库"}; for(String id : ids){ out.println(names[Integer.parseInt(id)-1]+"
"); } }else{ out.println("无任何商品浏览记录!
"); } } }else{ out.println("无任何商品浏览记录!
"); } %> body> html>
- 重新打开浏览器,在地址栏中输入http://localhost:8080/cookies/02_cookie/show.jsp。
- 浏览几本书籍后,在商品显示页面的浏览记录中,会显示已浏览的书籍内容。
- 至此,该案例已经完成。但仔细测试案例,会发现在点击第二本书籍后,服务器端响应的Cookie信息共有两个。
之所以效果正确的原因,在于浏览器使用了第二个Cookie信息中的history的值。出现这种问题的根本原因在于Servlet中处理Cookie的逻辑代码出现逻辑混乱。上述Servlet代码可以进行优化。
- 创建一个名为“CookieUtil”的工具类,用于判断Cookie是否存在对应信息。
public class CookieUtil { public static Cookie findCookie(Cookie[] cookies, String name){ if(cookies != null){ for (Cookie cookie : cookies) { String cookieName = cookie.getName(); if(cookieName.equals(name)){ return cookie; } return null; }else{ return null; } } }
- 将之前的Servlet代码进行改写如下。
public class ShowServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1 通过Request对象获取请求参数ID String id = request.getParameter("id"); //2 通过Request对象获取Cookie信息 Cookie[] cookies = request.getCookies(); //3 调用CookieUtil类的findCookie()方法,查找对应Cookie信息 Cookie cookie = CookieUtil.findCookie(cookies, "history"); //4 判断名为"history"的Cookie信息是否存在 if (cookie == null) {// 不存在名为"history"的Cookie // 创建用于存储商品浏览记录的Cookie实例对象 Cookie resCookie = new Cookie("history", id); // 设置当前Cookie的生命为1天 resCookie.setMaxAge(60*60*24); // 设置当前Cookie的有效路径为当前Web应用程序下所有目录 resCookie.setPath("/"); // 将当前Cookie添加到Response对象中 response.addCookie(resCookie); }else{// 存在名为"history"的Cookie // 获取Cookie的value值 String value = cookie.getValue(); // 将Cookie的value值以","切割 String[] ids = value.split(","); // 遍历所有书籍ID for (String eachId : ids) { // 判断当前ID是否已经存在Cookie信息中 if(!eachId.equals(id)){// 表示当前ID不存在Cookie信息中 // 创建Cookie实例对象,将当前书籍ID添加到Cookie信息中 Cookie resCookie = new Cookie("history", value + "," + id); // 设置当前Cookie的生命为1天 resCookie.setMaxAge(60*60*24); // 设置当前Cookie的有效路径为当前Web应用程序下所有目录 resCookie.setPath("/"); // 将当前Cookie添加到Response对象中 response.addCookie(resCookie); } } } response.setContentType("text/html;charset=utf-8"); response.getWriter().println("商品浏览成功!返回 "); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 打开浏览器,在地址栏中输入http://localhost:8080/cookies/02_cookie/show.jsp。浏览第二本书籍后,服务器端响应的Cookie信息只有一个。
显示浏览商品的逻辑优化完成后,最后来完成清空浏览商品记录信息(实际就是删除Cookie信息即可)。
- 创建一个Servlet用于清空浏览商品记录信息。
public class ClearServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Cookie cookie = new Cookie("history", ""); cookie.setMaxAge(0); cookie.setPath("/"); response.addCookie(cookie); response.sendRedirect("/cookies/02_cookie/show.jsp"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 配置Web工程的web.xml文件。
xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>display-name> <servlet> <servlet-name>ClearServletservlet-name> <servlet-class>app.java.cookie.ClearServletservlet-class> servlet> <servlet-mapping> <servlet-name>ClearServletservlet-name> <url-pattern>/clearurl-pattern> web-app>
- 重新发布Web工程到Tomcat服务器,并启动Tomcat服务器。
- 打开浏览器,在地址栏中输入http://localhost:8080/cookies/02_cookie/show.jsp。浏览几本书籍后,点击“清空记录”链接。