Web开发会话技术

56 阅读12分钟

一、会话

1. 基本介绍

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

2. 会话的过程出现的问题

  • 每个用户在使用浏览器与服务器进行会话的过程中,不可避免各自会产生一些数据,服务器要想办法为每个用户保存这些数据
  • 例如:多个用户点击超链接通过一个Servlet各自购买了一个商品,服务器应该想办法把每一个用户购买的商品保存在各自的地方,以便于这些用户点结帐Servlet时,结帐Servlet可以得到用户各自购买的商品为用户结帐

3. 会话的两种技术

  • Cookie
  • Session

二、Cookie

1. Cookie有什么用

大家在访问某个网站的时候,是否能看到提示你上次登录网站的时间,而且要注意的是不同,用户上次登录的时间肯定是不一样的,这是怎么实现的,使用Cookie

大家在访问某个购物网站的时候,是否能看到提示你曾经浏览过的商品,不同用户浏览过的商品肯定不一样,这是怎么实现的,使用Cookie

2. Cookie技术

Cookie(小甜饼)是客户端技术,服务器把每个用户的数据以Cookie的形式写给用户各自的浏览器。当用户使用浏览器再去访问服务器中的Web资源时,就会带着各自的数据去。这样Web资源处理的就是用户各自的数据了

image-20240129224332220

访问百度,F12选择应用程序就能看到

image-20240129225127940

Cookie是服务器在客户端保存用户的信息,比如登录名,浏览历史等, 就可以以Cookie方式保存

Cookie信息就像是小甜饼(cookie 中文)一样,数据量并不大,服务器端在需要的时候可以从客户端/浏览器读取(http 协议),可以通过图来理解

image-20240129225720069

3. Cookie可以用来做啥

  1. 保存上次登录时间等信息
  2. 保存用户名,密码, 在一定时间不用重新登录
  3. 网站的个性化,比如定制网站的服务,内容

4. Cookie基本使用

可以参考这个文档:

image-20240129230512218

Cookie有点象一张表(K-V),分两列,一个是名字,一个是值,数据类型都是 String , 如图

image-20240129230611904

1.如何创建一个Cookie(在服务端创建的)

Cookie cookie=new Cookie(String name,String val);

//保存时间
cookie.setMaxAge();	

2.如何将一个Cookie添加到客户端

response.addCookie(c);

3.如何读取Cookie(在服务器端读取到 cookie 信息)

request.getCookies();

5. Cookie的案例

演示Cookie底层实现机制,创建和读取Cookie

public class SetCookieServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1.创建一个Cookie对象
        //username是cookie的名字,名字要唯一,可以理解为是一个key
        //可以创建多个cookie对象
        Cookie username = new Cookie("username", "olivier");
        Cookie password = new Cookie("password", "pwd");

        //2.设置response类型和编码
        response.setContentType("text/html; charset=utf-8");

        //3.将Cookie发送给浏览器,让浏览器将该Cookie保存起来
        response.addCookie(username);
        response.addCookie(password);

        //4.writer写入返回
        PrintWriter writer = response.getWriter();
        writer.write("<h1>创建cookie成功</h1>");
        writer.flush();
        writer.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

这样就包含了这些Cookie

image-20240130214838806

看一下后台

image-20240130214845961

和上面说的一致

image-20240130214931423

读取Cookie

public class ReadCookieServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1.通过request读取cookie
        Cookie[] cookies = request.getCookies();

        //2.遍历cookies
        String msg = "";
        if (cookies != null && cookies.length != 0) {
            for (Cookie cookie : cookies) {
                msg += cookie.getName() + "-->" + cookie.getValue() + "\r\n";
                System.out.println(cookie.getName() + "-->" + cookie.getValue());
            }
        }

        //3.给浏览器返回信息
        response.setContentType("text/html; charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write("<h1>" + msg + "</h1>");
        writer.flush();
        writer.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

需求2:在服务器端读取指定的Cookie,给定Cookie的name,返回Cookie的值,如果不存在该Cookie,返回null

写一个Util

public class CookieUtils {
    //返回指定的cookie值
    public static Cookie readCookieByName(String name, Cookie[] cookies){
        //遍历
        if (name == null || "".equals(name) || cookies == null || cookies.length == 0) {
            return null;
        }

        for (Cookie cookie : cookies){
            if (name.equals(cookie.getName())){
                return cookie;
            }
        }
        return null;
    }
}

获取对应的Cookie

public class ServletGetOne extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Cookie[] cookies = request.getCookies();
        Cookie cookie = CookieUtils.readCookieByName("username", cookies);
        if (cookie != null) {
            System.out.println(cookie.getName() + " -- " + cookie.getValue());
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

需求:修改cookie,给定一个cookie的name,如果没找到,提示没有这个cookie,如果有,就修改它的值

public class UpdateCookieServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Cookie[] cookies = request.getCookies();
        Cookie cookie = CookieUtils.readCookieByName("username", cookies);
        response.setContentType("text/html; charset=utf-8");
        PrintWriter writer = response.getWriter();
        if (cookie != null){
            cookie.setValue("newOlivier");
            response.addCookie(cookie);
            writer.write("<h1>修改成功!</h1>");
        }else {
            writer.write("<h1>没有这个cookie</h1>");
        }
        writer.flush();
        writer.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

注意需要修改Cookie的话,需要set后再add回去,这样浏览器才能保存信息

另外如果创建一个同名的Cookie,就相当于覆盖

6. JSESSIONID

JSessionID是用来确定是哪个用户来访问服务器的,不同的用户JSessionID不同,而且不同的会话,JSessionID也不同,要验证新的会话,JSessionID不同,需要关闭浏览器重新打开才行

7. Cookie的生命周期

Cookie的生命周期指的是如何管理Cookie什么时候被销毁

setMaxAge()

  1. 正数,表示在指定的秒数后过期
  2. 负数,表示浏览器关闭,Cookie就会被删除(默认值是-1)
  3. 0,表示马上删除Cookie

如果不设置Cookie的声明周期的话,默认setMaxAge(-1) ,也就是关闭

需求:演示Cookie的生命周期

public class LifeCycleServlet 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 {
        Cookie cookie = new Cookie("lifeCycle", "testLifeCycle");
        //设置为30s,则30s之后就失效了,可能不会立刻从浏览器删除,但是关闭浏览器会消失
        //cookie.setMaxAge(30);
        //创建完立刻被删除
        //cookie.setMaxAge(0);
        //创建完除非关闭浏览器才会删除
        cookie.setMaxAge(-3);
        response.addCookie(cookie);
        System.out.println("add结束");
    }
}

同理,要想删除Cookie,需要设置cookie.setMaxAge(0),这样

8. Cookie的有效路径

有效路径规则:Cookie的path属性可以有效的过滤哪些Cookie可以发送给服务器,哪些不发。path属性是通过请求的地址来进行有效的过滤

image-20240130231122314

规则如下:

如果设置了如下规则

  • cookie1.setPath = /工程路径
  • cookie2.setPath = /工程路径/aaa

在请求http://ip:端口/工程路径/资源时

  • cookie1会发给服务器
  • cookie2不会发给服务器

在请求http://ip:端口/工程路径/aaa/资源时

  • cookie1 会发给服务器
  • cookie2 会发给服务器

需求:演示上面这个例子

public class CookiePathServlet extends HttpServlet {
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Cookie cookie1 = new Cookie("cookie1","cookie1Instance");
        Cookie cookie2 = new Cookie("cookie2","cookie2Instance");
        cookie1.setPath(request.getContextPath());
        System.out.println("contextpath = " + request.getContextPath());
        cookie2.setPath(request.getContextPath() + "/aaa");
        response.addCookie(cookie1);
        response.addCookie(cookie2);

        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write("设置路径成功");
        writer.flush();
        writer.close();
    }
}

当在读取的时候,规则是这样的,请求路径如果比设置的路径更具体,则Cookie会被请求到,如果更宽泛或者根本不匹配则Cookie请求不到

9. Cookie的注意事项和细节

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

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

  3. Cookie的总数量没有限制,但是每个域名的COOKIE数量和每个COOKIE的大小是有限制(不同的浏览器限制不同, 知道即可),Cookie不适合存放数据量大的信息

  4. 注意,删除Cookie时,path必须一致,否则不会删除

  5. Java servlet中Cookie中文乱码解决办法:如果存放中文的Cookie,默认报错,可以通过URL编码和解码来解决,不建议存放中文的Cookie信息

public class SetChineseCookieServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String lisi = URLEncoder.encode("李四", "UTF-8");
        String lisiName = URLEncoder.encode("李四的名字", "UTF-8");
        Cookie cookie = new Cookie(lisi,lisiName);

        response.addCookie(cookie);
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write("中文Cookie设置成功");
        writer.flush();
        writer.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

中文的Cookie会报错

image-20240131203716740

如果通过URLEncoder编码后再传入即可:

image-20240131204911703

解码使用URLDecoder:

public class GetChineseCookieServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Cookie[] cookies = request.getCookies();
        String cookieName = URLEncoder.encode("李四", "UTF-8");
        Cookie cookie = CookieUtils.readCookieByName(cookieName, cookies);
        if (cookie != null){
            String value = cookie.getValue();
            String decodeValue = URLDecoder.decode(value,"utf-8");
            System.out.println("value = " + decodeValue);
        }

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

三、Session

1. Session有什么用

不同的用户登录网站后,不管该用户浏览该网站的哪个页面,都可显示登录人的名字,还可以随时去查看自己的购物车中的商品, 是如何实现的?

也就是说,一个用户在浏览网站不同页面时,服务器是如何知道是张三在浏览这个页面,还是李四在浏览这个页面?

Cookie不太可行,虽然可以实现功能,但是不能存储过大的信息,也不应存放敏感信息,而且它是和浏览器走的,在公共场合可能存在泄露隐私的危险

2. Session技术

Session是服务器端技术,服务器在运行时为每一个用户的浏览器创建一个其独享的Session对象/集合

由于Session为各个用户浏览器独享,所以用户在访问服务器的不同页面时,可以从各自的Session中读取/添加数据, 从而完成相应任务

3. Session基本原理

image-20240131211413457

image-20240131211424124

当用户打开浏览器,访问某个网站,操作Session时,服务器就会在内存(在服务端)为该浏览器分配一个Session对象,该Session对象被这个浏览器独占, 如上图

这个Session对象也可看做是一个容器/集合,Session对象默认存在时间为30min(这是在tomcat/conf/web.xml),也可修改

image-20240131211734770

服务器是怎么区分服务器中的Session是属于哪个用户的呢?通过JSESSIONID来确定

3. Session可以用来做啥

  1. 网上商城中的购物车
  2. 保存登录用户的信息
  3. 将数据放入到Session中,供用户在访问不同页面时,实现跨页面访问数据
  4. 防止用户非法登录到某个页面
  5. .....

4. 如何理解Session

Session存储结构示意图

image-20240131212200304

你可以把Session看作是一容器类似HashMap,有两列(K-V),每一行就是Session的一个属性

每个属性包含有两个部分,一个是该属性的名字(String),另外一个是它的值(Object)

5. Session的常用方法

  1. 创建和获取Session
HttpSession hs=request.getSession();

第1次调用是创建Session会话,之后调用是获取创建好的Session对象

  1. 向Session添加属性
hs.setAttribute(String name,Object val);
  1. 从Session得到某个属性
Object obj=hs.getAttribute(String name);
  1. 从Session删除调某个属性
hs.removeAttribute(String name);
  1. 判断是不是刚创建出来的Session
isNew(); 

每个Session都有1个唯一标识Id值。通过getId()得到Session的会话id值,也就是JSESSIONID,通过它区分每个用户的Session

6. Session底层实现机制

image-20240131213027978

更细节的图解

image-20240131221451413

7. Session案例

public class CreateSession extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html; charset=utf-8");
        //1.获取Session
        HttpSession session = request.getSession();
        //2.获取SessionID
        System.out.println("sessionID=" + session.getId());
        //3.存数据
        session.setAttribute("email","zhangsan@qq.com");
        //4.回复
        PrintWriter writer = response.getWriter();
        writer.write("<h1>创建session成功</h1>");
        writer.flush();
        writer.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

会发现浏览器的JSESSIONID和后台的一致

image-20240131223838739

image-20240131223905468

第一次请求会出现set-Cookie:JSESSIONID,第二次请求就只在请求里携带了,响应就不带了,因为第一次没有创建需要创建并返回给浏览器,第二次已经有JSESSIONID,所以请求的时候就携带了,响应的时候就没有了

image-20240131224105239

获取Session

public class ReadSession extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. 获取session
        HttpSession session = request.getSession();
        //2. 读取属性
        Object email = session.getAttribute("email");
        if (email != null){
            System.out.println("session属性email = " + (String) email);
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

8. Session生命周期

  1. public void setMaxInactiveInterval(int interval)设置Session的超时时间(以秒为单位),超过指定的时长,Session就会被销毁
  2. 值为正数的时候,也就是设定Session的超时时长
  3. 负数表示永不超时
  4. public int getMaxInactiveInterval()获取Session的超时时间
  5. public void invalidate()让当前Session会话立即无效
  6. 如果没有调用setMaxInactiveInterval()来指定Session的生命时长,Tomcat会以Session默认时长为准,Session默认的超时为30分钟, 可以在Tomcat的web.xml设置
  7. Session的生命周期指的是:客户端/浏览器两次请求最大间隔时长,而不是累积时长。即当客户端访问了自己的 session,session 的生命周期将从 0 开始重新计算(指的是同一个会话两次请求之间的间隔时间)
  8. 声明周期怎么维护的,底层:Tomcat用一个线程来轮询会话状态,如果某个会话的空闲时间超过设定的最大值,则将该会话销毁

案例:

创建Session

public class CreateSession2 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        System.out.println("sessionId = " + session.getId());
        session.setMaxInactiveInterval(60);
        session.setAttribute("username","zhangsan");

        response.setContentType("text/html; charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write("<h1>创建session2成功,生命周期是60s</h1>");
        writer.flush();
        writer.close();
    }
}

读取Session

public class ReadSession2 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 {
        HttpSession session = request.getSession();
        Object username = session.getAttribute("username");
        if (username != null){
            System.out.println("username =  " + (String) username);
        }

        response.setContentType("text/html; charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write("<h1>username =  " + (String) username + "</h1>");
        writer.flush();
        writer.close();
    }
}

读取完等60s,发现属性已经没有了

测试删除

public class ServletDelete extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        session.invalidate();

        response.setContentType("text/html; charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write("delete success");
        writer.flush();
        writer.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

session.invalidate();相当于直接删除session的所有属性,如果只想删除某一个Session属性,就使用session.removeAttribute("xxx");