session&cookie

1.1 cookie简介

cookie是1993年由网景公司前雇员发明的一种进行网络会话状态跟踪的技术。

会话是由一组请求与响应组成,是围绕着一种相关事情所进行的请求与响应。所以这些请求与响应之间一定是需要有数据传递的,即需要进行会话状态跟踪。然而HTTP协议是一种无状态协议,在不同的请求之间是无法进行数据传递的。此时就需要一种可以进行请求之间进行数据传递的会话跟踪技术,而cookie就是一种这样的技术。

cookie是由服务器生成,保存在客户端的一种信息载体。这个载体中存放着用记访问该站点的会话状态信息。只在cookie没有被清空,或者cookie没有失效,那么,保存在其中的会话状态就有效。

用户在提交第一次请求后,由服务器生成cookie,并将其封装到响应头中,以响应的形式发送给客户端。客户端接收到这个响应后,将cookie保存到客户端。当客户端再次发送同类请求后(资源路径相同,资源名称不同),在请求中会携带保存在客户端的cookie数据,发送到客户端,由服务器对会话进行跟踪。

cookie技术并不是jvavaweb开发的专属技术,而是属于web开发的技术,是所有web开发语言均支持的技术。

cookie是由若干键值对构成,键值对均为字符串。

1.2 javaEE中的cookie

在javaEE中的javax.servlet.http包中存在一个类Cookie,就是用于完成会话跟踪的Cookie。其只有一个带参构造器。

默认绑定路径

访问路径由资源路径与资源名称构成。默认情况下,cookie与访问路径中的资源路径绑定。只要用户发出带有绑定资源路径的请求,则在请求头部,将会自动携带与之绑定的cookie路径。

生成cookie
public class SomeServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().append("Served at: ").append(request.getContextPath());
        //创建两个Cookie
        Cookie cookie = new Cookie("company","abc");
        Cookie cookie2 = new Cookie("teacher","def");

        //指定cookie绑定的资源路径。必须加上项目名称
        cookie.setPath(request.getContextPath() + "/aaa/bbb/ccc");
        cookie2.setPath(request.getContextPath() + "/ddd/eee/fff");

        //设置cookie有效期
        //大于0,表示将cookie存放到客户端硬盘
        //小于0,与不设置一样,会将cookie存放到浏览器的缓存
        //等于0,表示cookie一生成,马上失效
        cookie.setMaxAge(60 * 60);
        cookie2.setMaxAge(60 * 60 * 24 * 10);

        //向响应中添加Cookie
        response.addCookie(cookie);
        response.addCookie(cookie2);

    }
}
获取cookie
public class OhterServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().append("Served at: ").append(request.getContextPath());

        Cookie[] cookies = request.getCookies();

        for(Cookie cookie:cookies) {
            System.out.println(cookie.getName() + "===" + cookie.getValue());
            if(cookie.getName().equals("teacher") && cookie.getValue().equals("aaa")) {
                //...
            } else {
                //..
            }
        }
    }

}

2.1 HttpSession

session,即会话,是web开发中的一种会话状态跟踪技术。不同的是,cookie是将会话状态保存在了客户端,而session是将会话状态保存在了服务器端。

在javaweb开发中,session是以javax.servlet.HttpSession的接口对象的形式出现。


对于request的getSession()的用法:

  • 一般情况下,若要向session域中写入数据,则需要使用getSession(true),即getSession()方法。意味着有老的session用老的,没有老的则创建新的。

  • 若要从session中读取数据,则需要使用getSession(false)。意味着有老的session用老的,没有老的返回null。


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <form action = "FirstServlet" method = "POST">
        用户名:<input type = "text" name = "username" />
        <input type = "submit" value = "提交" />
    </form>
</body>
</html>

FirstServlet

public class FirstServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取用户提交的数据
        String username = request.getParameter("username");

        //将参数放到request域
        request.setAttribute("user", username);

        //获取session对象
        HttpSession session = request.getSession();

        //向session域中写入数据
        session.setAttribute("username", username);

        response.getWriter().print("SomeServlet" + username);

    }

}

SecondServlet

public class SecondServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //从request域中读取user属性
        Object user = request.getAttribute("user");

        //获取session
        HttpSession session = request.getSession(false);

        //从session中读取指定属性
        String username = null;
        if(session != null) {
            username = (String) session.getAttribute("username");
        }

        PrintWriter out = response.getWriter();
        out.println("user = " + user);
        out.println("username = " + username);
        out.println("session = " + session);
    }

}

2.2 Session的工作原理

在服务器中系统会为每个会话维护一个Session。不同的会话,对应不同的Session。那么,系统是如何识别Session对象的?即是如何做到在同一个会话过程中,一直使用的是同一个Session对象呢?

(1)、写入Session列表

服务器对当前应用中的Session是以Map的形式进行管理的,这个Map称为Session列表。该Map的key为一个32位长度的随机串,这个随机串称为JSessionID,value则为Session对象的引用。

当用户每一次提交请求时,服务端Servlet中执行到request.getSession()方法后,会自动生成一个Map.entry对象,key为一个根据某种算法新生成的JSessionID,value则为新创建的HttpSession对象。

(2)、服务器生成并发送cookie

在将session信息写入session列表后,系统还会自动将“JSessionID”作为name,这个32位长度的随机串作为value,以cookie的形式存放到响应报头中,并随着响应,将该cookie发送到客户端。

http://localhost:8080/sessioncookie
提交表单,跳转到FirstServlet后,

Response Headers

Content-Length: 13

Date: Fri, 27 Apr 2018 07:54:40 GMT

Set-Cookie: JSESSIONID=DCCE10CC2F5A6FAE40B716CA91B01EB0; Path=/sessioncookie; HttpOnly
(3)、客户端接收并发送cookie

客户端接收到这个cookie后会将其存放到浏览器缓存中。即,只要客户端浏览器不关闭,浏览器缓存中的cookie就不会消失。

当用户提交第二次请求时,会将缓存中的这个cookie,伴随着请求的头部信息,一块发送到服务端。

再次发送请求时,http://localhost:8080/sessioncookie/SecondServlet

Request Headers

...

Cookie: JSESSIONID=DCCE10CC2F5A6FAE40B716CA91B01EB0

Host: localhost:8080

(4)、从session列表中查找

服务端从请求中读取到客户端发送来的cookie,并根据cookie的SeesionID的值,从Map中查找相应key所对应的value,即session对象。然后,该session的域属性进行读写操作。

2.3 Session的失效

若某个session在指定的时间内一直未被访问,那么session将超时,即将失效。

在web.xml中可以通过<session-config />标签设置session的超时时间,单位为分钟。默认的超时时间为30分钟。时间从最后一次访问开始计时。

<session-config>
    <session-timeout>120</session-timeout>
</session-config>

若未到超时时间,也可以通过HttpSession中的方法invalide(),使得session失效。

2.4 cookie禁用后的session

cookie禁用后,从someServlet跳转到otherServlet时,

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取用户提交的数据
        String username = request.getParameter("username");

        //获取session对象
        HttpSession session = request.getSession();

        //向session域中写入数据
        session.setAttribute("username", username);

        //跳转到otherServlet
        response.sendRedirect(request.getContextPath() + "/otherServlet");    
    }

someServlet发送到客户端的包含有sessionid的cookie并没有被客户端保存,所以在发送otherServlet请求时,没有将该包含有sessionid的cookie发送到服务端,因此otherServlet服务端找不到该session,也就不能读取里面的数据。

解决方法:

request.encodeRedirectURL()方法

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取用户提交的数据
    String username = request.getParameter("username");

    //获取session对象
    HttpSession session = request.getSession();

    //向session域中写入数据
    session.setAttribute("username", username);

    //跳转到otherServlet
    String uri = request.getContextPath() + "/otherServlet";
    uri = request.encodeRedirectURL(uri);
    response.sendRedirect(uri);    
}

这样的话,跳转到otherServlet时,会将sessionid包含到访问地址中。

http://localhost/8080/sessioncookie/otherServlet;jsessionid=6BD1D909077DF0798ASSSJAK8200EC12

这样的话,发送请求到OtherServlet时,就会将该sessionid发送到服务端。

2.5 cookie禁用后非重定向跳转时session的跟踪

HttpServletResponse具有一个方法encdodeURL(),可以完成类似超链接这样的非重定向页面跳转的URL的重写,即在其路径后自动添加jsessionid

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取用户提交的数据
    String username = request.getParameter("username");

    //获取session对象
    HttpSession session = request.getSession();

    //向session域中写入数据
    session.setAttribute("username", username);

    //跳转到otherServlet
    response.setContentType("text/html;charset=utf-8");
    PrintWriter out = response.getWriter();
    String uri = "otherServlet";
    uri = response.encodeURL(uri);
    out.println("<a href='" + uri + "'>跳转</a>到otherservlet");
}