JavaWEB会话与状态管理(1)---Cookie与Session

本文介绍了在HTTP无状态协议下,如何通过Cookie和Session实现Web服务器的会话跟踪。讲解了Cookie的工作原理,包括创建、设置、读取Cookie的方法,并对比了会话Cookie和持久Cookie。接着探讨了Session的概念,描述了Session的创建、销毁以及生命周期。最后,讨论了URL重写在Cookie禁用情况下的Session跟踪作用,以及在开发中路径选择的建议。
摘要由CSDN通过智能技术生成

前提

提出问题

  • HTTP协议是一种无状态协议,WEB服务器本身不能识别出哪些请求是同一个浏览器发出的,浏览器的每一次请求都是完全孤立的
  • 即使HTTP1.1支持持续连接,但当用户有一段时间没有提交请求,连接也会关闭
  • 怎么才能实现网上商店中的购物车呢:某个用户从网站的登录页面登入后,再进入购物页面购物时,负责处理购物请求的服务器程序必须知道处理上一次请求的程序所得到的用户信息。
  • 作为web服务器,必须能够采用一种机制来唯一地标识一个用户,同时记录该用户的状态会话和会话状态
  • WEB应用中的会话是指一个客户端浏览器与WEB服务器之间连续发生的一系列请求和响应过程
  • WEB应用的会话状态是指WEB服务器与浏览器在会话过程中产生的状态信息,借助会话状态,WEB服务器能够把属于同一会话中的一系列的请求和响应过程关联起来

如何实现有状态的会话

  • WEB服务器端程序要能从大量的请求信息中区分出哪些请求信息属于同一个会话,即能识别出来自同一个浏览器的访问请求,这需要浏览器对其发出的每个请求信息都进行标识:属于同一个会话中的请求消息都附带同样的标识号,而属于不同会话的请求消息总是附带不同的标识号,这个标识号就称之为会话ID(SessionID)。
  • 在Servlet规范中,常用Cookie和Session两种机制完成会话跟踪

Cookie机制

    采用的是在客户端保持HTTP状态信息的方案。

    Cookie是在浏览器访问WEB服务器的某个资源时,由WEB服务器在HTTP响应消息头中附带传送给浏览器一个小文本文件。

    一旦WEB浏览器保存了某个Cookie,那么它在以后每次访问该WEB服务器时,都会在HTTP请求头中将这个Cookie回传给WEB服务器。

    底层的实现原理:WEB服务器通过在HTTP响应消息中增加Set-Cookie响应头字段将Cookie消息发送给浏览器,浏览器则通过在HTTP请求消息中增加Cookie请求头字段将Cookie回传给WEB服务器。

    一个Cookie只能标识一种信息,它至少含有一个标识该信息的名称(NAME)和设置值(VALUE)。

    一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个WEB浏览器也可以存储多个WEB站点提供的Cookie。

    浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。

Cookie的传送过程示意图

在Servl程序中使用Cookie

    Servlet API中提供了一个javax.servlet.htp.Cookie类来封装Cookie信息,它包含有生成Cookie信息和提取Cookie信息的各个属性的方法。

    Cookie类的方法:

  • 构造方法:public Cookie (String name , String value)
  • getName方法
  • setValue与getValue方法
  • setMaxAge与getMaxAge方法
  • setPath与getPath方法

    HttpServletResponse接口中定义了一个addCookie方法,它用于在发送给浏览器的HTTP响应消息中增加了一个Set-Cookie响应头字段。

    HttpServletRequest接口定义了一个getCookies方法,它用于从HTTP请求信息的Cookie请求头字段中读取所有的Cookie项

package org.lanqiao.cookie.demo;

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;

@WebServlet("/cookieServlet")
public class CookieServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");
       PrintWriter  out =  response.getWriter();
        //1 创建cookie
        Cookie cookie = new Cookie("name","lanqiao");
        //2 设置cookie的最大有效期
        cookie.setMaxAge(60*60*24);
        //3 将cookie回传给客户端
        response.addCookie(cookie);
        //4 获取cookile  通过request的getCookies()  获取到的是当前请求中的所有的cookie   因此 这里得到的是一个数组
       Cookie[] cookies =  request.getCookies();
       // 5 对获取的cookie数组进行遍历
       for(Cookie  cook: cookies){
           //6 获取每个cookie的名称
          //  String   cookName  = cook.getName();
            //7 判断获取到的cookie的名称是否是我们所关心的cookie  i如果是  则 获取当前cookie的值
            //if(cookName.equals("name")){
                out.print(cook.getName() +"-------"+cook.getValue());
           // }
       }
    }
}

Cookie的发送

    1、创建Cookie对象

    2、设置最大时效

    3、将Cookie放入到HTTP响应报头

    如果创建了一个cookie,并将他发送到浏览器,默认情况下它是一个会话级别的cookie;存储在浏览器的内存中,用户退出浏览器之后被删除。若希望浏览器将该cookie存储在磁盘上,则需要使用maxAge,并给出一个以秒为单位的时间。将最大时效设为0则是命令浏览器删除该cookie。

    发送cookie需要使用HttpServletResponse的addCookie方法,将cookie插入到一个Set-Cookie HTTP响应报头中。由于这个方法并不修改任何之前指定的Set-Cookie报头,而是创建新的报头,因此将这个方法称为是addCookie,而非setCookie。

Cookie的读取

    1、调用request.getCookies。要获取浏览器发送来的cookie,需要调用HttpServletRequest的getCookies方法,这个调用返回Cookie对象的数组,对应由HTTP请求中Cookie报头输入的值。

    2、对数组进行循环,调用每个cookie的getName方法,直到找到感兴趣的cookie为止。

会话Cookie和持久Cookie的区别

    如果不设置过期时间,则表示这个Cookie生命周期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不保存在硬盘而是保存在内存里。

    如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie依然有效直到超过设定的过期时间。

    存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存的cookie,不同的浏览器有不同的处理方式。

使用Cookie进行简易自动登录

新建一个页面

<%--
  Created by IntelliJ IDEA.
  User: huwei
  Date: 2018/7/15
  Time: 10:56
  To change this template use File | Settings | File Templates.
--%>

<%@ page contentType="text/html;charset=UTF-8" session="false"  language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
   <%--<a href="/cookieServlet">cookie</a>--%>
   <%=request.getSession().isNew()%>
  <form action="/loginServlet" method="post">
      账号:<input name="username" type="text" value=""><br><br>
      密码:<input name="pwd" type="password" value=""><br><br>
      <input type="submit" value="提交">
  </form>
  </body>
</html>

再建一个LoginServlet

package org.lanqiao.cookie.demo;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

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.net.URLDecoder;
import java.net.URLEncoder;

@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1 从客户端的请求中获取用户名和密码
        //2 验证用户名和密码
        //3 如果用户名和密码正确,  将用户的登陆信息保存到cookie中 则直接跳转到success.jsp
        // 4 如果不正确  重新跳转到登陆页面
        //5  如果客户端没有获取到输入的用户名和密码  则需要从cookie去检索用户名和密码  如果正确  则直接跳转到success.jsp
        /*
        在cookie保存对象的实现方式:
            可以采用JSON  将该对象转换为一个JSON字符串   需要对转化之后的json字符串实行编码 保存在cookie中
            当需要该信息的时候   可以从cookie中获取这个json字符串(需要解码)  然后 把他重新转换为我们的对象  这样就可已使用了
         */
        String username = request.getParameter("username");
        String password  = request.getParameter("pwd");
        User user = null;
        if(username != null && !username.trim().equals("")){
            if(username.equals("lanqiao") && password.equals("123456")){
                user = new User(username,password);
                //将一个对象转换成json字符串的时候   我们需要对该对象实行编码
                String  userStr = JSON.toJSONString( user);
                Cookie  cookie = new Cookie("user",URLEncoder.encode(userStr,"utf-8"));
                /*
                在cookie保存中文时  也同样需要采用URLEncoder对其进行编码   在获取的时候  同样采用URLDecode进行解码
                 */
                Cookie cookie1 = new Cookie("name","张三");
                cookie.setMaxAge(60*60*24);
                response.addCookie(cookie);
                response.addCookie(cookie1);

            }
        }else{
             Cookie[] cookies =    request.getCookies();
             for (Cookie cook : cookies){
                 String cookName = cook.getName();
                 if(cookName.equals("user")){
                     //再次获取到该对象之后 需要进行解码
                  JSONObject  jObj =  JSON.parseObject(URLDecoder.decode(cook.getValue(),"utf-8"));
                  String  u = jObj.getString("username");
                  String p = jObj.getString("password");
                  user = new User(u,p);

                 }
                 if(cookName.equals("name")){
                     System.out.println("name = " + cook.getValue());
                 }
             }
        }
        if(user !=null){
            request.setAttribute("user",user);
            request.getRequestDispatcher("/success.jsp").forward(request,response);
        }else{
            request.getRequestDispatcher("/index.jsp").forward(request,response);
        }
    }
}

再来个User对象,进行封装

package org.lanqiao.cookie.demo;

import java.util.Objects;

public class User {
   private  String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(username, user.username) &&
                Objects.equals(password, user.password);
    }

    @Override
    public int hashCode() {

        return Objects.hash(username, password);
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

再来个登录成功页面

<%@ page import="org.lanqiao.cookie.demo.User" %><%--
  Created by IntelliJ IDEA.
  User: huwei
  Date: 2018/7/15
  Time: 11:40
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
        <%
            User user = (User)request.getAttribute("user");
        %>
    你好:<%=user.getUsername()%>
</body>
</html>

Session

    中文翻译为会话,其本来含义是指有始有终的一系列动作/消息,比如打电话是从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个session。

    session在Web开发环境下的语义又有了新的扩展,它的含义是指一类用来在客户端与服务器端之间保持状态的解决方案,有时候session也用来指解决方案的存储结构。

    在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象,注意:一个浏览器独占一个Session对象(默认情况下)。因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户数据写到用户浏览器独占的session中,当用户使用浏览器访问其他程序时,其他程序可以从用户的session中去除该用户的数据,为用户服务。

Session和Cookie区别:

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

保存session id的几种方式

    保存session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照贵族把这个标识发送给服务器。

    服务器创建session出来后,会把session的id号,以cookie的形式回写给客户机,这样,只要客户机的浏览器不关,再去访问服务时,都会带着session的id号去,服务器发现客户机浏览器带session id过来了,就会使用内存中与之对应的session为之服务。

    由于cookie可以被人为的禁用,必须有其它的机制以便在cookie被禁用时仍然能够把session id传递回服务器,经常采用的一种技术叫做URL重写,就是把session id,既必须在每个客户端可能请求的路径后面都包含这附加在URL后面。网络在整个交互过程中始终保持状态session id。

Session Cookie

    session通过Session ID来区分不同的客户,session是以cookie或URL重写为基础的,默认使用cookie来实现,系统会创造一个名为JSESSIONID的输出cookie,这称之为session cookie,以区别persistent cookies(也就是我们通常说的cookie),session cookie是存储与浏览器内存中的,并不是写到硬盘上的,通常看不到JSESSIONID,但是当把浏览器的cookie禁止后,web服务器会采用URL重写的方式传递Sessionid,这时地址栏可以看到

    session cookie针对某一次会话而言,会话结束session cookie也就随着消失了,而presistent cookie知识存在于客户端硬盘上的一段文本。

    关闭浏览器,只会是浏览器端内存里的session cookie消失,但不会使保存在服务器端的session对象消失,同样也不会使已经保存到硬盘上的持久化cookie消失。

Session的持久化

<%

    Cookie  cookie = new Cookie("JSESSIONID" , session.get());

    cookie.setMaxAge(20);

    response.addCookie(cookie);

%>

HttpSession的常用方法

  • getId(),获取当前的会话ID。每个会话在服务器都存在一个唯一的标识sessionID,session对象发送到浏览器的唯一数据就是sessionID,它一般存储在cookie中
  • getCreationTime()和getLastAccessedTime()方法可以获取会话创建的时间和最后访问的时间,但其返回值是毫秒,一般需要使用下面的转换来获取具体日期和时间:

                                Date creationTime = new Date(session.getCreationTime());

                                Date accessedTime = new Date(session.getLastAccessedTime());

  • setMaxInactiveInterval(int interval),设置会话的最大持续时间,单位是秒,负数标明会话永不失效。
  • getMaxInActiverval(),获取会话的最大持续时间
  • isNew()判断session是否是新创建的
  • invalidate(),使session失效,可以立即使当前会话失效,原来会话中存储的所有对象都不能再被访问。
  • setAttribute(String name,Object value),设定指定名字的属性的值,并将它添加到session会话范围内,如果这个属性是会话范围内存在,则更改该属性的值。
  • getAttribute(String name),删除指定名字的session属性,若该属性不存在,则出现异常。

先建一个页面

<%@ page import="java.util.Date" %><%--
  Created by IntelliJ IDEA.
  User: huwei
  Date: 2018/7/15
  Time: 15:00
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>session</title>
</head>
<body>
  SESSIONID:  <%=session.getId()%><br><br>
  Session的有效期:<%=session.getMaxInactiveInterval()%><br><br>
  Session的创建时间:<%=new Date(session.getCreationTime())%><br><br>
  Session的访问时间:<%=new Date(session.getLastAccessedTime())%><br><br>
  Session是否新建:<%=session.isNew()%><br><br>
  <form action="/sessionServlet" method="post">
        用户名:<input type="text" name="username"     value=""><br><br>
      <input type="submit" value="提交">
  </form>

</body>
</html>

再建一个SessionServlet.Java

package org.lanqiao.session.demo;
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;
import java.io.IOException;

@WebServlet("/sessionServlet")
public class SessionServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            String username = request.getParameter("username");
            username = new String(username.getBytes("ISO-8859-1"),"utf-8");
            System.out.println("username = " + username);
            // 1 通过request获取session
             HttpSession  session  = request.getSession();
           //2 将获取的用户数据保存在session中
           session.setAttribute("username",username);
           request.getRequestDispatcher("/loginSuccess.jsp").forward(request,response);


    }
}

再建一个登录成功跳转页面

<%--
  Created by IntelliJ IDEA.
  User: huwei
  Date: 2018/7/15
  Time: 15:11
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8"  language="java" %>
<html>
<head>
    <title>success</title>
</head>
<body>
欢迎您:<%=session.getAttribute("username")%><br><br>
<a href="/logoutServlet"> 安全退出</a>

</body>
</html>

Session的生命周期

创建:

    1、什么时候创建HttpSession对象?对于JSP:是否浏览器访问服务端的任何一个JSP,服务器都会立即创建一个HttpSession对象呢?

    不一定。若当前JSP是客户端访问的当前WEB应用的第一个资源,且JSP的page指定的session属性值为false,则服务器就不会为JSP创建一个HttpSession对象;若当前JSP不是客户端访问的当前WEB应用的第一个资源,且其他页面以及创建一个HttpSession对象,则服务器也不会为当前JSP页面创建一个HttpSession对象,而会把和当前会话关联的那个HttpSession对象返回给当前的JSP页面

    2、对于Servlet:若Servlet是客户端访问的第一个WEB应用的资源,则只有调用了request.getSession()或request.getSession(true)才会创建HttpSession对象

    3、page指令的session = “false”到底表示什么意思?

当前JSP页面禁用session隐含变量,但可以使用其他的显式的HttpSession对象

    4、在Servlet中如何获取HttpSession对象?

  •  request.getSession(boolean create):

     create 为 false, 若没有和当前 JSP 页面关联的 HttpSession 对象, 则返回 null; 若有, 则返回 true

    create 为 true, 一定返回一个 HttpSession 对象. 若没有和当前 JSP 页面关联的 HttpSession 对象, 则服务器创建一个新的

    HttpSession 对象返回, 若有, 直接返回关联的.

  • request.getSession(): 等同于 request.getSession(true)

    5、什么时候销毁 HttpSession 对象?

  • 直接调用 HttpSession 的 invalidate() 方法: 该方法使 HttpSession 失效
  •  服务器卸载了当前 WEB 应用.
  • 超出 HttpSession 的过期时间.

   获取HttpSession的过期时间:session.getMaxInactiveInterval();   设置 HttpSession 的过期时间: session.setMaxInactiveInterval(5); 单位为秒

  • 在Tomcat的 web.xml 文件中设置 HttpSession 的过期时间: 单位为分钟

                                    <session-config>
                                                    <session-timeout>30</session-timeout>

                                    </session-config>

 

  • 并不是关闭了浏览器就销毁了 HttpSession.

URL重写

利用URL重写实现Session跟踪

当cookie被禁用时,如何跟踪Session?

       Servlet规范中引入一种补充的会话管理机制,它允许不支持Cookie的浏览器也可以与WEB服务器保持连续的会话。这种补充机制要求在响应消息的实体内容中必须包含下一次请求的超链接,并将会话标识号作为超链接的URL地址的一个特殊参数。

      将会话标识号以参数形式附加在超链接URL地址后面的技术称为URL重写。如果在浏览器不支持的Cookie或者关闭了Cookie功能的情况下,WEB服务器还要能够与浏览器实现有状态的会话,就必须对所有可能被客户端访问的请求路径(包括超链接、form表单的action属性设置和重定向的URL)进行URL重写。

       HttpServletRespouse接口中定义了两个用于完成URL重写方法:

  • encodeURL方法
  • encodeRedirectURL方法

       encodeURL()及encodeRedirectURL()方法首先判断cookies是否被浏览器支持;如果支持,则参数URL被原样返回,session ID将通过cookies来维持

路径与相对路径

       在实际开发中,我们不免要遇到从页面到页面,从页面经servlet到页面之间的跳转时,我们应该使用绝对路径还是相对路径呢?

       在实际开发中,建议使用绝对路径,绝对路径是肯定不会出现问题的,相对路径可能会存在问题。

       在由Servlet跳转到jsp页面时,此时浏览器地址栏上显示的是Servlet的路径,而若jsp页面的超链接还是相对于该jsp页面的地址,则可能会出现路径混乱的问题。

       编写绝对路径来避免上面的问题

在javaweb中什么叫“绝对路径”?

       相对于contextPath的路径,即任何的路径都必须带上contextPath

       比如:http://localhost:8080/contextPath(当前WEB应用的上下文路径)/a.jsp

如何解决?

  • 在这里我们要先提一下JavaWeb开发中的“/”到底表示什么?

       当前WEB应用的根路径:http://localhost:8080/contextPath/: 若/交给servlet容器来处理

----请求转发时:request.getRequestDispacher("/path/b.jsp").forward(request,response);

----Web.xml文件中映射servlet访问路径

                                   <servlet-mapping>

                                                <servlet-name>TestServlet</servlet-name>

                                                <url-pattern>/TestServlet</url-pattern>

                                  </servlet-mapping>

       WEB站点的根路径:http://localhost:8080/: 若/交给浏览器来处理

---超链接:<a href="TestServlet">去BBB界面</a>

---表达中的action:<from action="/test.jsp">

---做请求重定向的时候:response.sendRedirect("/a.jsp")

  • 解决方法

       如果 / 代表的是站点的根目录,在其前面加上contextPath,这个contextPath可以由request或application的getContextPath()方法来获取

                                     <a href="<%=request.getContextPath()%>/path/c.jsp">去CCC界面</a>

这样上面的错误就解决了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值