• 03、JavaEECookie & Session


    Cookie

    会话可简单理解为:用户打开一个浏览器,点击多个超链接访问服务器多个Web资源,然后关闭浏览器的过程。

    有状态会话:一个同学来过教室,下次再来教室,我们会知道这个同学曾经来过,这称之为有状态会话。

    每个用户在使用浏览器与服务器进行会话过程中,不可避免各自产生一些数据,程序要想办法为每个用户保存这些数据。

    基础入门

    保存会话的技术有两种,它们分别如下:Cookie和Session

    Cookie是客户端技术,程序把每个用户的数据以cookie形式写给用户各自的浏览器,当用户使用浏览器去访问服务中的Web资源时,就会携带各自的数据过去。这样Web资源处理的就是各个用户的数据了。

    Session是服务端技术,服务器在运行时可以为每个用户的浏览器创建一个独享的Session对象,由于Session未用户浏览器独享,所以用户在访问服务器的Web资源时,可以把各自的数据放在各自的Session中,当用户再去访问服务器中的其他Web资源时,其他Web资源再从各自的Session对象中取出数据来为用户服务。

    Cookie常用API

    Java中的javax.servlet.http.Cookie类用于创建一个Cookie

    方法 描述
    Cookie(String name, String value) 实例化Cookie对象,传入cooke名称和cookie的值。
    public String getName() 取得Cookie的名字
    public String getValue() 取得Cookie的值
    public void setValue(String newValue) 设置Cookie的值
    public void setMaxAge(int expiry) 设置Cookie的最大保存时间
    public int getMaxAge() 获取Cookies的有效期
    public void setPath(String uri) 设置cookie的有效路径
    public String getPath() 获取cookie的有效路径
    public void setDomain(String pattern) 设置cookie的有效域
    public String getDomain() 获取cookie的有效域

    request接口中定义了一个getCookies()方法,它用于获取客户端提交的Cookie。

    response接口也中定义了一个addCookie()方法,它用于在其响应过程增加一个相应的Set-Cookie头字段。

    范例:使用cookie记录上一次访问时间

    public class RequestDemo3 extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //  解决中文乱码问题
            response.setCharacterEncoding("UTF-8");
            response.setContentType("text/html;charset=UTF-8");
            
            PrintWriter writer = response.getWriter();
            Cookie[] cookies = request.getCookies();
            // cookies为空表示是第一次访问
            if (cookies != null) {
                writer.print("您上一次访问网页的时间是:");
                for (int i = 0; i < cookies.length; i++) {
                    Cookie cookie = cookies[i];
                    if ("lastAccessTime".equals(cookie.getName())) {
                        long parseLong = Long.parseLong(cookie.getValue());
                        Date date = new Date(parseLong);
                        writer.write(date.toString());
                    }
                }
            }else {
                writer.write("这是您第一次访问网页!");
            }
                
            //用户访问过之后重新设置用户的访问时间,存储到cookie中,然后发送到客户端浏览器
            Cookie cookie = new Cookie("lastAccessTime", String.valueOf(System.currentTimeMillis()));
            response.addCookie(cookie);
        }
        public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            doGet(request, response);
        }
    }
    

    首先我们将浏览器的cookies都清理掉,然后访问该servlet会得到如下结果:

    点击浏览器的刷新按钮,进行第二次访问,此时就服务器就可以通过cookie获取浏览器上一次访问的时间了,效果如下:

    上面的实例中没有使用setMaxAge()设置cookie的有效期,cookie在浏览器关闭后就失效,如果想让cookie依然有效,必须设置有效期。

    //用户访问过之后重新设置用户的访问时间,存储到cookie中,然后发送到客户端浏览器
    Cookie cookie = new Cookie("lastAccessTime", System.currentTimeMillis()+"");
    //设置Cookie的有效期为1天
    cookie.setMaxAge(24 * 60 * 60);
    //将cookie对象添加到response对象中,这样服务器在输出response对象中的内容时就会把cookie也输出到客户端浏览器
    response.addCookie(cookie);
    

    这样即使关闭了浏览器,下次再访问时,也依然可以通过cookie获取用户上一次访问的时间。

    Cookie注意细节

    Cookie包含如下需要注意的点:

    一个Cookie只能标识一种信息,它至少包含有一个标识该信息的名称(NAME)和值(VALUE)。
    一个Web站点可以给一个浏览器发送多个Cookie,浏览器也可以存储多个Web站点提供的Cookie。
    浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie大小限制为4kb。
    在没有设置Cookie有效期的情况下,当浏览器退出时间,Cookie会被删除。如果想Cookie存储在磁盘上,则需要设置有效期。

    删除Cookie

    在删除Cookie时,必须保持path一致,否则无法删除:

    public class CookieDemo1 extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            //创建一个名字为lastAccessTime的cookie
            Cookie cookie = new Cookie("lastAccessTime", System.currentTimeMillis()+"");
            //将cookie的有效期设置为0,命令浏览器删除该cookie
            cookie.setMaxAge(0);
            response.addCookie(cookie);
        }
        public void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            doGet(request, response);
        }
    }
    

    Cookie中存取中文

    要想在cookie中存储中文,那么必须使用URLEncoder类里面的encode(String s, String enc)方法进行中文转码,例如:

    Cookie cookie = new Cookie("userName", URLEncoder.encode("小明", "UTF-8"));
    response.addCookie(cookie);
    

    在获取cookie中的中文数据时,再使用URLDecoder类里面的decode(String s, String enc)进行解码,例如:

    URLDecoder.decode(cookies[i].getValue(), "UTF-8")
    

    Session

    在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个session对象(默认情况下)。因此,

    在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的session中,当用户使用浏览器访问其它程序时,其它程序可以从用户的

    session中取出该用户的数据,为用户服务。

    Session和Cookie的主要区别如下:

    Cookie是把用户的数据写给用户的浏览器。
    Session技术把用户的数据写到用户独占的session中。
    Session对象由服务器创建,开发人员可以调用request对象的getSession方法得到session对象。

    Session使用

    Session实现原理

    服务器创建Session出来后,会将session的id号以cookie形式回馈给浏览器,只要浏览器在不关闭的情况下再次访问服务器时,都会携带session的id号过去。

    服务器发现浏览器携带过来的session的id后,会通过id来指定内存中与之对应的session为之服务。

    public class SessionDemo1 extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            response.setCharacterEncoding("UTF=8");
            response.setContentType("text/html;charset=UTF-8");
            //使用request对象的getSession()获取session,如果session不存在则创建一个
            HttpSession session = request.getSession();
            //将数据存储到session中
            session.setAttribute("data", "小明");
            //获取session的Id
            String sessionId = session.getId();
            //判断session是不是新创建的
            if (session.isNew()) {
                response.getWriter().print("session创建成功,session的id是:"+sessionId);
            }else {
                response.getWriter().print("服务器已经存在该session了,session的id是:"+sessionId);
            }
        }
        public void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            doGet(request, response);
        }
    }
    

    第一次访问时,服务器会创建一个新的sesion,并且把session的Id以cookie的形式发送给客户端浏览器,如下图所示:

    点击刷新按钮,再次请求服务器,此时就可以看到浏览器再请求服务器时,会把存储到cookie中的session的Id一起传递到服务器端:

    request.getSession()方法内部新创建了Session之后一定是做了如下的处理:

    //获取session的Id
    String sessionId = session.getId();
    //将session的Id存储到名字为JSESSIONID的cookie中
    Cookie cookie = new Cookie("JSESSIONID", sessionId);
    //设置cookie的有效路径
    cookie.setPath(request.getContextPath());
    response.addCookie(cookie);
    

    浏览器禁用Cookie处理

    IE8禁用cookie

    工具->internet选项->隐私->设置->将滑轴拉到最顶上(阻止所有cookies)

    解决方案:

    response.encodeRedirectURL(java.lang.String url) 用于对sendRedirect方法后的url地址进行重写。
    response.encodeURL(java.lang.String url)用于对表单action和超链接的url地址进行重写
    

    使用Servlet共享Session中的数据来解决浏览器禁用Cookie的问题:

    IndexServlet:

    // 列出所有图书
    public class IndexServlet extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter writer = response.getWriter();
            HttpSession session = request.getSession();
            writer.write("本网站有如下图书:<br/>");
            Set<Entry<String,Book>> entrySet = DBUtils.getAlll().entrySet();
            for (Entry<String, Book> entry : entrySet) {
                Book book = entry.getValue();
                String url = request.getContextPath()+ "/servlet/BuyServlet?id=" + book.getId();
                //  将超链接的url地址进行重写
                url = response.encodeUrl(url);
                writer.write(book.getName() + "   <a href='" + url + "'>购买</a><br/>");
                
            }
        }
        public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            doGet(request, response);
        }
    }
    

    DBUtils类和Book类:

    // 模拟数据库
    public class DBUtils {
        private static Map<String, Book> map = new LinkedHashMap<String, Book>();
        static{
            map.put("1", new Book("1", "javaweb开发"));
            map.put("2", new Book("2", "spring开发"));
            map.put("3", new Book("3", "hibernate开发"));
            map.put("4", new Book("4", "structs开发"));
            map.put("5", new Book("5", "ajax开发"));
        }
        
        public static Map<String, Book> getAlll(){
            return map;
        }
    }
    // Book类
    public class Book {
        private String id;
        private String name;
        public Book(String id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
        public String getId() {
            return id;
        }
        public void setId(String id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
    

    ListCartServlet:

    public class ListCartServlet extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter writer = response.getWriter();
            HttpSession session = request.getSession();
            List<Book> list = (List<Book>) session.getAttribute("list");
            if (null == list || list.size() <= 0) {
                writer.write("对不起,您还没有购买任何商品!!");
                return;
            }
            
            // 显示用户购买过的商品
            writer.write("您购买过如下商品:<br/>");
            for (Book book : list) {
                writer.write(book.getName() + "<br/>");
            }
            
        }
        public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            doGet(request, response);
        }
    }
    

    BuyServlet:

    public class BuyServlet extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            String id = request.getParameter("id");
            // 得到用户想要购买的书籍
            Book book = DBUtils.getAlll().get(id);
            HttpSession session = request.getSession();
            // 得到用户用于保存的所有书籍
            List<Book> list = (List) session.getAttribute("list");
            if (null == list || list.size() <= 0) {
                list = new ArrayList<Book>();
                session.setAttribute("list", list);
            }
            list.add(book);
            //response. encodeRedirectURL(java.lang.String url)用于对sendRedirect方法后的url地址进行重写
            String url = response.encodeRedirectUrl(request.getContextPath() + "/servlet/ListCartServlet");
            System.out.println(url);
            response.sendRedirect(url);
        }
        public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            doGet(request, response);
        }
    }
    

    在禁用了cookie的IE8下的运行效果如下:

    通过查看IndexServlet生成的html代码可以看到,每一个超链接后面都带上了session的Id,如下所示

    本网站有如下书:<br/>
    javaweb开发   <a href='/JavaWeb_Session_Study_20140720/servlet/BuyServlet;jsessionid=96BDFB9D87A08D5AB1EAA2537CDE2DB2?id=1'>购买</a><br/>
    spring开发   <a href='/JavaWeb_Session_Study_20140720/servlet/BuyServlet;jsessionid=96BDFB9D87A08D5AB1EAA2537CDE2DB2?id=2'>购买</a><br/>
    hibernate开发   <a href='/JavaWeb_Session_Study_20140720/servlet/BuyServlet;jsessionid=96BDFB9D87A08D5AB1EAA2537CDE2DB2?id=3'>购买</a><br/>
    struts开发   <a href='/JavaWeb_Session_Study_20140720/servlet/BuyServlet;jsessionid=96BDFB9D87A08D5AB1EAA2537CDE2DB2?id=4'>购买</a><br/>
    ajax开发   <a href='/JavaWeb_Session_Study_20140720/servlet/BuyServlet;jsessionid=96BDFB9D87A08D5AB1EAA2537CDE2DB2?id=5'>购买</a><br/>
    

    所以,当浏览器禁用了cookie后,就可以用URL重写这种解决方案解决Session数据共享问题。而且response. encodeRedirectURL(java.lang.String url) 和response. encodeURL(java.lang.String url)是两个非常智能的方法,当检测到浏览器没有禁用cookie时,那么就不进行URL重写了。我们在没有禁用cookie的火狐浏览器下访问,效果如下:

    ![img](file:///C:/Users/Legend/Documents/My Knowledge/temp/e0f7d0ba-0575-49e7-b762-3f06a62a310e/128/index_files/201833071467404.gif)

    从演示动画中可以看到,浏览器第一次访问时,服务器创建Session,然后将Session的Id以Cookie的形式发送回给浏览器,response. encodeURL(java.lang.String url)

    方法也将URL进行了重写,当点击刷新按钮第二次访问,由于火狐浏览器没有禁用cookie,所以第二次访问时带上了cookie,此时服务器就可以知道当前的客户端浏览器

    并没有禁用cookie,那么就通知response. encodeURL(java.lang.String url)方法不用将URL进行重写了。

    Session创建和销毁时机

    Session对象的创建时机

    在程序中第一次调用request.getSession()方法时就会创建一个新的Session,可以用isNew()方法来判断Session是不是新创建的

    //使用request对象的getSession()获取session,如果session不存在则创建一个
    HttpSession session = request.getSession();
    //获取session的Id
    String sessionId = session.getId();
    //判断session是不是新创建的
    if (session.isNew()) {
        response.getWriter().print("session创建成功,session的id是:" + sessionId);
    }else {
        response.getWriter().print("服务器已经存在session,session的id是:" + sessionId);
    }
    

    Session对象的销毁时机

    session对象默认30分钟没有使用,则服务器会自动销毁session,在web.xml文件中可以手工配置session的失效时间,例如:

    <?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>
      
      <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
      </welcome-file-list>
      <!-- 设置Session的有效时间:以分钟为单位-->
        <session-config>
            <session-timeout>15</session-timeout>
        </session-config>
    </web-app>
    

    当需要在程序中手动设置Session失效时,可以手工调用session.invalidate方法,摧毁session。

    HttpSession session = request.getSession();
    //手工调用session.invalidate方法,摧毁session
    session.invalidate();
    

    Session防止表单重复提交

    表单重复提交场景

    在开发中,如果网速比较慢的情况下,用户提交表单发现服务器没有响应,那么用户可能以为是自己没有提交表单,则会再次点击提交按钮来重复提交表单

    所以在开发中,我们必须防止这一情况。

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
    <!DOCTYPE HTML>
    <html>
      <head>
        <title>Form表单</title>
      </head>
      
      <body>
          <form action="${pageContext.request.contextPath}/servlet/DoFormServlet" method="post">
            用户名:<input type="text" name="username">
            <input type="submit" value="提交" id="submit">
        </form>
      </body>
    </html>
    

    form表单提交到DoFormServlet进行处理:

    public class DoFormServlet extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response) 
                throws ServletException, IOException {
            request.setCharacterEncoding("UTF-8");
            String username = request.getParameter("username");
            try {
                // 让当前的线程睡眠3秒钟来模拟网络延迟
                Thread.sleep(3 * 1000);
            }catch(Exception e) {
                e.printStackTrace();
            }
            System.out.println("向数据库中插入数据:" + username);
        }
        public void doPost(HttpServletRequest request, HttpServletResponse response) 
                throws ServletException, IOException {
            doGet(request, response);
        }
    }
    

    如果没有进行form表单重复提交处理,那么在网络延迟的情况下下面的操作将会导致form表单重复提交多次。

    场景一:
    在网络延迟的情况下用户有时间多次点击submit按钮导致表单重复提交。
    场景二:
    在表单提交后,用户点击刷新网页导致表单数据重复提交。
    场景三:
    用户提交表单后,点击浏览器的后退按钮回退到表单页面进行再次提交。

    JS防止表单重复提交

    防止表单重复提交比较常用的方式是采用JavaScirpt来防止表单重复提交,具体做法如下:

    修改上方form.jsp页面,添加如下JavaScirpt代码来防止表单重复提交

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
        <head>
            <title>form表单提交</title>
            <script type="text/javascript">
                var isCommited = false;
                function doSubmit() {
                    if (isCommited == false) {
                        isCommited = true;
                        return true;
                    }else {
                        return false;
                    }
                }
            </script>
            
        </head>
        <body>
            <form action="${pageContext.request.contextPath}/servlet/DoFormServlet"
                  onsubmit="return doSubmit()" method="post">
                用户名<input type="text" name="username">
                <input type="submit" value="提交" id="submit">
            </form>
        </body>
    </html>
    

    这样就即使多次点击提交按钮,也只会提交一次,就可以避免重复提交导致的问题。

    Session防表单重复提交

    在上方提到的三种重复提交的场景中,第二和第三种场景通过客户端无法解决,需要服务端利用session来解决这种重复提交的问题。

    方案:

    在服务端生成一个唯一的随机标识号,也就是Token(令牌),同时在当前用户的Session域中保存这个Token。然后将Token发送到客户端的的Form表单中,在Form表单中使用隐藏域来存储该Token,表单提交的时候连同该Token一起提交到服务端,然后服务端对该Token进行比较是否一致。不一致则是重复提交,此时服务端就可以不处理重复提交的表单了。处理完后再清楚服务端当前用户的Session域中存储的标识号即可。

    在下列情况下,服务器程序将拒绝处理用户提交的表单请求:

    存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。
    当前用户的Session中不存在Token(令牌)。
    用户提交的表单数据中没有Token(令牌)。

    a) 创建FormServlet,用于生成Token令牌和跳转到form.jsp页面:

    目前调试出现问题,下次再来调试记录一下......

  • 相关阅读:
    js获取input file完整路径的方法
    form提交表单上传图片
    基于HTML5的可预览多图片Ajax上传
    ie11兼容
    上传时获取文件的完整路径图片预览的js代码(兼容Firfox和IE)
    学习笔记
    ie浏览器兼容性(ie9,ie10)
    大学最后悔的事
    easyui 分页
    jQuery学习
  • 原文地址:https://www.cnblogs.com/pengjingya/p/14409879.html
Copyright © 2020-2023  润新知