Tomcat运行流程、Servlet运行原理以及常用API

news2024/11/24 11:06:32

文章目录

  • Servlet原理
    • Tomcat 的定位
    • Tomcat 的伪代码
      • Tomcat 初始化流程
      • Tomcat处理请求
      • 总结
  • Servlet的核心API
    • HttpServlet
    • HttpServletRequest
    • HttpServletResponse
    • Cookie 和 Session

Servlet原理

Servlet终究是属于应用层,它是在应用层进行的一系列操作,它的底层仍然是依赖传输层,网络层,数据链路层这些通信

换句话说,Servlet是属于上层建筑,下面的输层,网络层,数据链路层属于是底层基础,上下层之间是有比较紧密的联系

Tomcat 的定位

通过这张图,可以很明确Servlet和Tomcat的关系
在这里插入图片描述

虽然Tomcat在Servlet之下,但是Tomcat其实也是一个应用程序,运行在用户态的普通进程(Tomcat其实也是一个Java进程)
用户写的代码(根据请求计算相应),通过Servlet和Tomcat进行交互,Tomcat拿到数据之后,进一步的和浏览器之间进行网络传输(封装和分用)

用户和服务器之间交互详解图:

在这里插入图片描述

用户和服务器之间交互主要有三大过程

1)接收请求

1、用户在浏览器输入一个 URL,此时浏览器就会构造一个 HTTP 请求
2、这个 HTTP 请求会经过网络协议栈逐层进行封装成二进制的 bit 流, 最终通过物理层的硬件设备转换成光信号/电信号传输出去
3、这些承载信息的光信号/电信号通过互联网上的一系列网络设备,最终到达目标主机(这个过程也需要网络层和数据链路层参与)
4、服务器主机收到这些光信号/电信号,又会通过网络协议栈逐层进行分用,层层解析,最终还原成HTTP 请求,并交给 Tomcat 进程进行处理(根据端口号确定进程)
5、Tomcat 通过 Socket 读取到这个请求(一个字符串),并按照 HTTP 请求的格式来解析这个请求,根据请求中的 Context Path 确定一个 webapp,再通过 Servlet Path 确定一个具体的类,再根据当前请求的方法 (GET/POST/…),决定调用这个类的 doGet 或者 doPost 等方法。此时我们的代码中的doGet / doPost 方法的第一个参数 HttpServletRequest 就包含了这个 HTTP 请求的详细信息

2)根据请求计算响应

在我们的 doGet / doPost 方法中,就执行到了我们自己的代码。我们自己的代码会根据请求中的一些信息,来给 HttpServletResponse 对象设置一些属性。例如状态码,header,body 等

3)返回响应

1、我们的 doGet / doPost 执行完毕后,Tomcat 就会自动把 HttpServletResponse 这个我们刚设置好的对象转换成一个符合 HTTP 协议的字符串,通过 Socket 把这个响应发送出去
2、此时响应数据在服务器的主机上通过网络协议栈层层封装,最终又得到一个二进制的 bit 流,通过物理层硬件设备转换成光信号/电信号传输出去
3、这些承载信息的光信号/电信号通过互联网上的一系列网络设备,最终到达浏览器所在的主机(这个过程也需要网络层和数据链路层参与)
4、浏览器主机收到这些光信号/电信号,又会通过网络协议栈逐层进行分用,层层解析,最终还原成
HTTP 响应,并交给浏览器处理
5、浏览器也通过 Socket 读到这个响应(一个字符串),按照 HTTP 响应的格式来解析这个响应,并且把body中的数据按照一定的格式显示在浏览器的界面上

Tomcat 的伪代码

Tomcat 初始化流程

class Tomcat {
    // 用来存储所有的 Servlet 对象
    private List<Servlet> instanceList = new ArrayList<>();

    public void start() {
        // 根据约定,读取 WEB-INF/web.xml 配置文件;
        // 并解析被 @WebServlet 注解修饰的类

        // 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类.
        Class<Servlet>[] allServletClasses = ...;

        // 这里要做的的是实例化出所有的 Servlet 对象出来;
        for (Class<Servlet> cls : allServletClasses) {
            // 这里是利用 java 中的反射特性做的
            // 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的
            // 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是
            // 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。
            Servlet ins = cls.newInstance();
            instanceList.add(ins);
        }
        // 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;
        for (Servlet ins : instanceList) {
            ins.init();
        }

        // 利用我们之前学过的知识,启动一个 HTTP 服务器
        // 并用线程池的方式分别处理每一个 Request
        ServerSocket serverSocket = new ServerSocket(8080);
        // 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
        ExecuteService pool = Executors.newFixedThreadPool(100);

        while (true) {
            Socket socket = ServerSocket.accept();
            // 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
            pool.execute(new Runnable() {
                doHttpRequest(socket);
            });
        }
        // 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
        for (Servlet ins : instanceList) {
            ins.destroy();
        }
    }

    public static void main(String[] args) {
        new Tomcat().start();
    }
}

小结

1)让Tomcat先从指定的目录中找到所有要加载的Servlet类
部署的时候,是把Servlet代码编译成了.class,然后打了war包,然后拷贝到了webapps里面
Tomcat就会从webapps里来找到哪些.class对应的Servlet类,并且需要进行加载
在这里插入图片描述

当然啦,这里的加载不一定是Tomcat一启动就立即执行,也可能是"懒加载"
但是此处伪代码中就假设立即加载了

2)根据刚才类加载的结果,通过反射的方式给这些类创建Servlet实例
在这里插入图片描述
3)实例创建好之后,就可以调用当前Servlet实例的init方法了
在这里插入图片描述

Servlet自带的方法,默认情况下init啥都不干。我们在继承个HttpServlet的时候,也可以自己重写init,就可以在这个阶段,帮我们做一些初始化工作了

4)创建TCP socket,监听8080端口,等待有客户端来连接
在这里插入图片描述
5)如果循环退出了,Tomcat也要结束了,就会依次循环调用每个Servlet的destroy方法
在这里插入图片描述
这个是属于收尾工作(Tomcat退出之前的事情)
细心的人可能会发现:明明前面是while(true),里面也并没有break,为啥执行流还能走到这里?
因为我们写的是一个伪代码,只包含了核心逻辑,没有太多的实现细节。实际上,Tomcat里面会有一些条件来退出这个循环。和init类似,这里的destroy默认也是啥也不干,可以在用户代码中重写这destroy
虽然这里有destroy这个环节,但这个环节并不是十分靠谱
如果Tomcat是通过“正常流程”退出的,才能主动结束循环,调用这里的destroy。如果是通过“非正常流程”退出的,此时就来不及调用destroy
“正常流程”比如:Tomcat的8005端口(管理端口,通过这个端口可以对这个Tomcat发号施令)。通过这个方式来关闭Tomcat,就属于“正常流程"
“非正常流程”:直接结束进程(大部分是这种情况)

Tomcat处理请求

class Tomcat {
    void doHttpRequest(Socket socket) {
        // 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析,和响应构建
        HttpServletRequest req = HttpServletRequest.parse(socket);
        HttpServletRequest resp = HttpServletRequest.build(socket);

        // 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态内容
        // 直接使用我们学习过的 IO 进行内容输出
        if (file.exists()) {
        // 返回静态内容
            return;
        }

        // 走到这里的逻辑都是动态内容了
        	
        // 根据我们在配置中说的,按照 URL -> servlet-name -> Servlet 对象的链条
        // 最终找到要处理本次请求的 Servlet 对象
        Servlet ins = findInstance(req.getURL());

        // 调用 Servlet 对象的 service 方法
        // 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
        try {
            ins.service(req, resp);
        } catch (Exception e) {
        // 返回 500 页面,表示服务器内部错误
        }
    }
}	

class Servlet {
    public void service(HttpServletRequest req, HttpServletResponse resp) {
        String method = req.getMethod();
        if (method.equals("GET")) {
            doGet(req, resp);
        } else if (method.equals("POST")) {
            doPost(req, resp);
        } else if (method.equals("PUT")) {
            doPut(req, resp);
        } else if (method.equals("DELETE")) {
            doDelete(req, resp);
        } 
        ......
    }
}

小结

1、构造req和resp
在这里插入图片描述

req,是通过读取socket中的数据,然后再按照HTTP协议的请求格式来解析的构造成了一个HttpServletRequest对象。resp这里则是相当于new了一个空的对象
2、判断当前请求的资源是否为静态文件
在这里插入图片描述

如果是静态文件,就读取文件内容,把文件内容构造到,resp对象的body中,并且返回这个resp对象
3、动态资源的处理
在这里插入图片描述

根据请求的URL,来获取到使用哪个类来处理
URL里要有两级路径:
第一级路径: Context Path,确定一个webapp
第二级路径: Servlet Path,确定一个Servlet类
如果没有找到匹配的Servlet类,就会返回404
4、找到对应的Servlet对象,进一步调用service方法
在service方法内部,又会进一步的调用 doGet / doPost等方法
在这里插入图片描述
如果在执行Servlet的service方法过程中出现未处理的异常,就会返回500

总结

在讨论到上面这整套流程过程中,涉及到了关于Servlet的关键方法,主要有三个

init: 初始化阶段,对象创建好了之后,就会执行到.用户可以重写这个方法,来执行一些初始化逻辑
service: 在处理请求阶段来调用,每次来个请求都要调用一次service
destroy: 退出主循环,tomcat结束之前会调用,用来释放资源~

我们把这个三个关键方法以及它们的调用时机称为Servlet的生命周期

Servlet的核心API

Servlet中有三个比较关键的类:HttpServletHttpServletRequestHttpServletResponse

HttpServlet

我们写 Servlet 代码的时候,首先第一步就是先创建类,继承自 HttpServlet,并重写其中的某些方法,然后才被Tomcat执行到的’

核心方法

方法名称调用时机
init在 HttpServlet 实例化之后被调用一次
destory在 HttpServlet 实例不再使用的时候调用一次
service收到 HTTP 请求的时候调用
doGet收到 GET 请求的时候调用(由 service 方法调用)
doPost收到 POST 请求的时候调用(由 service 方法调用)
doPut/doDelete/doOptions/…收到其他请求的时候调用(由 service 方法调用)

例1:doGet方法

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //这个是让服务器在自己的控制台里打印
        System.out.println("hello Servlet");

        //在页面上也能打印hello Servlet
        //把hello Servlet字符串放在http响应的body中,浏览器就会把body的内容显示到页面上
        resp.getWriter().write("hello Servlet");
    }

}

运行Tomcat,直接在浏览器中访问

在这里插入图片描述

例2:doPost方法

@WebServlet("/method")
public class MethodServlet extends HelloServlet{
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //系统的默认编码是JDK
        //显示的告诉浏览器,按照utf-8来读
        resp.setContentType("text/html;charset=utf8");
        resp.getWriter().write("POST 响应");
    }
}

此时还要写一个test.html页面,并且将它放在webapp下

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>

<body>
    <script src="https://lib.baomitu.com/jquery/3.6.0/jquery.js"></script>
    <script>
        $.ajax({
            type:'post',
            url:'method',//路径不加斜杠
            success:function(body){
                console.log(body);
            }
        });
    </script>
</body>

</html>

在这里插入图片描述

运行Tomcat,直接在浏览器中访问

在这里插入图片描述

注意:在同一个webapp里,多个Servlet关联的路径,不能相同,否则在启动Tomcat时,Tomcat就会自动退出

在这里插入图片描述

在这里插入图片描述

HttpServletRequest

Tomcat 通过 Socket API 读取 HTTP 请求(字符串),并且按照 HTTP 协议的格式把字符串解析成
HttpServletRequest 对象
也就是说HttpServletRequest 对应一个HTTP响应,HTTP响应中有什么,这里就有什么

核心方法

方法名称描述
String getProtocol()返回请求协议的名称和版本
String getMethod()返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT
String getRequestURI()从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分
String getContextPath()返回指示请求上下文的请求 URI 部分
String getQueryString()返回包含在路径后的请求 URL 中的查询字符串
Enumeration getParameterNames()返回一个 String 对象的枚举,包含在该请求中包含的参数的名称
String getParameter(Stringname)以字符串形式返回请求参数的值,或者如果参数不存在则返回null
String[] getParameterValues(Stringname)返回一个字符串对象的数组,包含所有给定的请求参数的值,如果参数不存在则返回 null
Enumeration getHeaderNames()返回一个枚举,包含在该请求中包含的所有的头名
String getHeader(Stringname)以字符串形式返回指定的请求头的值
String getCharacterEncoding()返回请求主体中使用的字符编码的名称
String getContentType()返回请求主体的 MIME 类型,如果不知道类型则返回 null
int getContentLength()以字节为单位返回请求主体的长度,并提供输入流,或者如果长度未知则返回 -1
InputStreamgetInputStream()用于读取请求的 body 内容,返回一个 InputStream 对象

例3:打印请求信息

@WebServlet("/showRequest")
public class ShowRequestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("<h3>首行部分</h3>");
        //获取协议名和版本
        stringBuilder.append(req.getProtocol());
        stringBuilder.append("<br>");
        //获取方法
        stringBuilder.append(req.getMethod());
        stringBuilder.append("<br>");
        //拿到请求路径
        stringBuilder.append(req.getRequestURI());
        stringBuilder.append("<br>");
        //获取上下文路径
        stringBuilder.append(req.getContextPath());
        stringBuilder.append("<br>");
        //获取URL中的查询字符串
        stringBuilder.append(req.getQueryString());
        stringBuilder.append("<br>");
        stringBuilder.append("<h3>header 部分</h3>");
        //获取请求中所有的头名,以枚举方式返回
        Enumeration<String> headerNames = req.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            //获取name
            String headerName =  headerNames.nextElement();
            //获取value
            String headerValue = req.getHeader(headerName);
            stringBuilder.append(headerName + ":" + headerValue + "<br>");
        }
        resp.setContentType("text/html; charset=utf8");
        //将所有信息写回到响应body中
        resp.getWriter().write(stringBuilder.toString());
    }
}

运行Tomcat,直接在浏览器中访问

在这里插入图片描述

上述API能够让我们拿到HTTP请求的各个方面内容,但是却没那么常用,更常用的,其实是getParameter这个方法(获取到query string中的详细内容)

例4:通过getParameter获取GET请求中的参数

@WebServlet("/getParameter")
public class GetParameterServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         //预期浏览器传来一个形如这样的请求:/getParameter?userId=827&classId=2090008
        String userId = req.getParameter("userId");
        String classId = req.getParameter("classId");
        resp.getWriter().write("userId=" + userId + ", classId" + classId);
    }
}

运行Tomcat,直接在浏览器中访问

在这里插入图片描述

路径和参数之间用"?“作为分隔符,参数与参数之间用”&"作为分隔符

POST请求body的格式主要有三种:

  1. x-www- form-urlencoded
  2. form-data
  3. json

相对来说,最长用的还是第一种和第三种方式,第二种主要是用于上传图片,上传文件用得比较多,因此这里我写了方式一和方式三

例5:请求是这种格式 x-www- form-urlencoded,服务器通过Post获取参数
获取参数的方式和GET一样,也是用getParameter

如何在前端构造一个这样的格式请求呢?
1)通过form表单
2)通过postman

这里我们采用form表单的方式

@WebServlet("/postGetParameter")
public class PostGetParameterServlet extends HttpServlet {


    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //预期浏览器传来一个形如这样的请求:/getParameter?userId=827&classId=2090008
        //服务器也是通过req.getParameter来获取到内容的
        String userId = req.getParameter("userId");
        String classId = req.getParameter("classId");
        resp.getWriter().write("userId=" + userId + ", classId=" + classId);
    }
}

现在还需要在webapp下构造一个html页面,和WEB-INF是同级目录,而不是在WEB-INF里面,否则大概率就会404

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>

<body>
    <form action="postGetParameter" method="post">
        <input type="text" name="userId">
        <input type="text" name="classId">
        <input type="submit" value="提交">
    </form>
</body>

</html>

运行Tomcat,直接在浏览器中访问

在这里插入图片描述
在这里插入图片描述

通过Fiddler抓包,可以看到Post的详细细节

在这里插入图片描述

例6:请求是这json格式,服务器通过Post获取参数

对于这种body为json的格式来说,如果手动来解析,其实并不容易(JSON里面的字段是能嵌套的)
像这种情况,手动处理比较麻烦,可以使用第三方的库,来直接处理json格式数据
Java生态中,用来处理JSON的第三库,种类也是很多,我主要使用的库,叫做Jackson (Spring 官方推荐的库)

通过maven把jackson这个库,给下载到本地,并引入到项目中

在这里插入图片描述

在这里插入图片描述

这么多版本,随便挑一个就行,这里我选的是2.12.6.1版本

在这里插入图片描述

复制这段内容,然后粘贴到pom.xml中

在这里插入图片描述

在浏览器前端代码中,通过js构造出body为json格式的请求

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>

<body>
    <!-- 构造json格式的请求,就不再使用form而是使用ajax -->
    <input type="text" id="userId">
    <input type="text" id="classId">
    <input type="button" value="提交" id="submit">

    <script src="https://lib.baomitu.com/jquery/3.6.0/jquery.js"></script>
    <script>
        let userIdInput = document.querySelector('#userId');
        let classIdInput = document.querySelector('#classId');
        let button = document.querySelector('#submit');
        button.onclick = function() {
            $.ajax({
                type:'post',
                url:'postJson',
                contentType:'application/json',
                data:JSON.stringify({
                    userId:userIdInput.value,
                    classId:classIdInput.value 
                }),
                success:function(body){
                    console.log(body);
                }
            });
        }
        
    </script>
</body>

</html>

在这里插入图片描述

在java后端代码中,通过jackson来进行处理

需要使用jackson ,把请求body中的数据读取出来,并且解析成Java中的对象

class User {
    public int userId;
    public int classId;
}

@WebServlet("/postJson")
public class PostJsonServlet extends HttpServlet {
    //1.创建一个Jackson的核心对象
    ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //2.读取body中的请求,然后使用ObjectMapper 来解析成需要的对象
        //readValue 就是把Json格式的字符串({"userld":"827","classld":"2090008"}),转换成Java中的对象

        //第一个参数,表示对哪个字符串进行转换,这个参数可以填写成一个String,也可以填一个InputStream对象,还可以填一个File对象
        //第二个参数,表示要把这个Json格式的字符串,转换成哪个Java对象
        User user = objectMapper.readValue(req.getInputStream(), User.class);//通过反射获取User类的属性
        resp.getWriter().write("userId: " + user.userId + ", classId: " + user.classId);
    }
}

readValue是怎么完成转换的?

  1. 先把getInputStream对应的流对象里面的数据都读取出来
  2. 针对这个json字符串进行解析,从字符串=>键值对。key: userld; value:827。key: classld; value: 2090008
  3. 遍历这个键值对,依次获取到每一个key,根据这个key的名字,和User类里面的属性名字进行对比,看看有没有匹配的名字,如果发现有匹配的属性名字,则把当前key对应的value赋值到User类中对应的属性上(赋值过程中同时会进行类型转换),如果没有匹配的属性,就跳过,取下一个key
  4. 当把所有的键值对都遍历过之后,此时User对象就被构造完成了

此处就要求,类的属性名得和键值对中的key的名字匹配(要求名字匹配,这个只是jackson默认行为)
如果你就非得想搞个不匹配的名字,也不是不可以,jackson也提供了其他方式帮助我们进行映射,这里就不多赘述了

运行Tomcat,直接在浏览器中访问

在这里插入图片描述

当前使用的是ajax的方式来提交数据,这个操作默认不会产生页面跳转,就和咱们使用form风格差别很大

通过Fiddler抓包,可以看到Post的详细细节

在这里插入图片描述

HttpServletResponse

Servlet 中的 doXXX 方法的目的就是根据请求计算得到相应,然后把响应的数据设置到HttpServletResponse 对象中,然后 Tomcat 就会把这个 HttpServletResponse 对象按照 HTTP 协议的格式,转成一个字符串,并通过Socket 写回给浏览器

核心方法

方法名称描述
void setStatus(int sc)为该响应设置状态码
void setHeader(String name, String value)设置一个带有给定的名称和值的 header。如果 name 已经存在,则覆盖旧的值
void addHeader(Stringname, String value)添加一个带有给定的名称和值的 header. 如果 name 已经存在,不覆盖旧的值, 并列添加新的键值对
void setContentType(Stringtype)设置被发送到客户端的响应的内容类型
void setCharacterEncoding(Stringcharset)设置被发送到客户端的响应的字符编码(MIME 字符集)例如,UTF-8
void sendRedirect(Stringlocation)使用指定的重定向位置 URL 发送临时重定向响应到客户端
PrintWriter getWriter()用于往 body 中写入文本格式数据
OutputStream getOutputStream()用于往 body 中写入二进制格式数据

例7:设置状态码为200

@WebServlet("/status")
public class StatusServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setStatus(200);
        resp.getWriter().write("hello");
    }
}

运行Tomcat,直接在浏览器中访问

在这里插入图片描述

通过Fiddler抓包

在这里插入图片描述

例8:自动刷新页面

@WebServlet("/autoRefresh")
public class AutoRefreshServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //每隔1秒钟刷新一次
        resp.setHeader("Refresh", "1");
        //加上时间戳,方便观察
        resp.getWriter().write("timeStamp: " + System.currentTimeMillis());
        
    }
}

运行Tomcat,直接在浏览器中访问

在这里插入图片描述

可以明显的看到,时间戳都不同

通过Fiddler抓包

在这里插入图片描述

例10:进行302重定向

@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //resp.setStatus(302);
        //resp.setHeader("Location", "https://www.baidu.com");

        //Servlet提供了一个更简便的实现重定向的写法,更推荐这种写法
        resp.sendRedirect("https://www.baidu.com");
    }
}

通过Fiddler抓包

在这里插入图片描述

Cookie 和 Session

HTTP 协议自身是属于 “无状态” 协议
无状态的含义:

默认情况下 HTTP 协议的客户端和服务器之间的这次通信,和下次通信之间没有直接的联系

但在实际的应用场景中,服务器是需要知道客户端最近有没有过访问,例如登录网站后,进行页面跳转,服务器就不应该让用户再次登录验证

在这里插入图片描述

图中的"令牌"通常就存在用户端的Cookie中

举个通俗易懂栗子,理解什么是Cookie

我们到食堂的一个窗口去吃麻辣烫,由于这个窗口人很多,服务员也不知道刚出锅的麻辣烫是哪个同学的,于是就会为每一个来吃麻辣烫的同学分发一个号码牌,自己再留一个对应的号码牌夹在对应的碗中,等到麻辣烫好了之后,就会有提示说,请xx号来取麻辣烫,而对应的同学就拿着号码牌去取麻辣烫

这里的号码牌就相当于客户端的Cookie

这个Cookie是服务器给的,自己不能伪造
此时在服务器这边就需要记录令牌信息, 以及令牌对应的用户信息, 这个就是 Session 机制所做的工作

核心方法

HttpServletRequest 类中的相关方法

方法名称描述
HttpSession getSession()在服务器中获取会话。参数如果为 true,如果会话存在,返回当前会话,会话不存在就新建会话。参数如果为 false,如果会话存在,返回当前会话,会话不存在返回 null
Cookie[] getCookies()返回一个数组,包含客户端发送该请求的所有的 Cookie 对象,会自动把Cookie 中的格式解析成键值对

在调用getSession的时候具体要做的事情:
1.创建会话

首先先获取到请求中cookie里面的sessionld字段(相当于会话的身份标识),判定这个sessionld是否在当前服务器上存在。如果不存在,则进入创建会话逻辑

创建会话,会创建一个HttpSession对象,并且生成一个sessionld (是一个很长的数字,通常是用十六进制来表示,能够保证唯一性)。接下来就会把这个sessionld作为key,把这个HttpSession对象,作为value,把这个键值对,给保存到服务器内存的一个"哈希表" 这样的结构中,实际中也不一定就真是哈希表,但一定是类似的能够存储键值对的结构,并且这个数据是在内存中的

再然后,服务器就会返回一个HTTP响应,把sessionld通过Set-Cookie字段返回给浏览器,浏览器就可以保存这个sessionld到Cookie中了

2.获取会话

先获取到请求中的cookie里面的sessionld字段(也就是会话的身份标识),判定这个sessionld是否在当前服务器上存在(也就是在这个哈希表中是否有)。如果有,就直接查询出这个HttpSession对象,并且通过返回值返回回去

HttpServletResponse 类中的相关方法

方法名称描述
void addCookie(Cookie cookie)把指定的 cookie 添加到响应中

HttpSession 类中的相关方法

一个 HttpSession 对象里面包含多个键值对,我们可以往 HttpSession 中存任何我们需要的信息

方法名称描述
Object getAttribute(Stringname)该方法返回在该 session 会话中具有指定名称的对象,如果没有指定名称的对象,则返回 nul
void setAttribute(Stringname, Object value)该方法使用指定的名称绑定一个对象到该 session 会话
boolean isNew()判定当前是否是新创建出的会话

Cookie 类中的相关方法

每个 Cookie 对象就是一个键值对

方法名称描述
String getName()该方法返回 cookie 的名称,名称在创建后不能改变(这个值是 Set-Cooke 字段设置给浏览器的)
String getValue()该方法获取与 cookie 关联的值
void setValue(StringnewValue)该方法设置与 cookie 关联的值

HTTP 的 Cooke 字段中存储的实际上是多组键值对,每个键值对在 Servlet 中都对应了一个 Cookie 对象
通过 HttpServletRequest.getCookies() 获取到请求中的一系列 Cookie 键值对
通过 HttpServletResponse.addCookie() 可以向响应中添加新的 Cookie 键值对

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/28270.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Prometheus Operator 实战 监控 etcd 集群

上节课和大家讲解了 Prometheus Operator 的安装和基本使用方法&#xff0c;这节课给大家介绍如何在 Prometheus Operator 中添加一个自定义的监控项。 除了 Kubernetes 集群中的一些资源对象、节点以及组件需要监控&#xff0c;有的时候我们可能还需要根据实际的业务需求去添…

Java 后端 本地调试-获取微信公众号 openId

Java 后端 本地调试-获取微信公众号 openId申请测试微信公众号内网穿透工具配置公众号获取用户 openId申请测试微信公众号 微信测试公众号 内网穿透工具 netapp 配置公众号 搜索网页账号选项 点击修改&#xff0c;填写内网穿透的域名 获取用户 openId 1 第一步&#xff…

国家高新技术企业的好处

国家高新技术企业的好处&#xff1a;享受税收减免优惠政策&#xff1b;国家科研经费支持和财政拨款&#xff1b;国家级的资质认证硬招牌&#xff1b;提升企业品牌形象&#xff1b;促进企业科技转型&#xff1b;提高企业市场价值&#xff1b;提高企业资本价值&#xff1b;吸引市…

【电脑讲解】电脑如何实现双系统

核心提示&#xff1a;电脑双系统&#xff0c;大家应该不会太陌生&#xff0c;有的网吧就装的是双系统&#xff0c;双系统可以满足不同人群的需要&#xff0c;可以这样说&#xff0c;一个系统可以专门工作使用&#xff0c;另一个可以供玩游戏使用&#xff0c;&#xff08;电脑硬…

[Linux打怪升级之路]-环境变量

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。 目录 一、认识环…

一文详解JVM的内存结构

目录 前言 内存结构 程序计数器 虚拟机栈 本地方法栈 堆内存 方法区 内部组成 前言 Java的JVM解决的问题是跨操作系统问题。程序员只需要专注于代码的编写&#xff0c;这些代码能够在不同的操作系统Mac&#xff0c;Linux和Windows运行的前提是JVM。JVM还提供了垃圾回收机制…

Linux终端操作-Xshell和Xftp(家庭版)

目录一&#xff0c;终端操作二&#xff0c;软件安装1&#xff0c;Xshell, Xftp下载2&#xff0c;Xshell安装3&#xff0c;Xftp安装三&#xff0c;使用1&#xff0c;Xshell建立连接2&#xff0c;Xftp上传文件一&#xff0c;终端操作 上一篇博客记录了如何本地安装虚拟机并实现本…

SpringSecurity(十五)---OAuth2的运行机制(上)-OAuth2概念和授权码模式讲解

一、前言 鸽了很久&#xff0c;其实也因为自己确实比较忙&#xff0c;加之自己在造demo的时候也遇到了很多问题&#xff0c;并且网上这方面的解答非常之少&#xff0c;不过也正是因为少&#xff0c;才更加让我想写这样的知识分享&#xff0c;最终&#xff0c;在一篇博客的解答…

中文drupal教程(4)Session会话系统

Session&#xff08;会话&#xff09;在网站中扮演非常重要的角色&#xff0c;储存临时用户数据、登录数据等等都用到了它&#xff0c;Drupal使用到了Symfony的Session组件&#xff0c;该组件非常强大灵活&#xff0c;drupal在此基础上有所改造和扩展&#xff0c;要理解Symfony…

企业微信接口测试实战(一)

本文为在霍格沃兹测试开发学社中学习到的一些技术,写出来分享给大家,希望有志同道合的小伙伴可以一起交流技术,一起进步~ 霍格沃茨启发: 测试开发进阶班>接口自动化测试>企业微信接口测试实战 企业微信接口测试实战 一、准备环境二、脚本实现2.1、 获得access_token2…

防火墙用户管理理论+实验

目录 注&#xff1a;实验需要有安全策略配置、NAT配置基础 一、防火墙用户管理重要知识点 用户管理 访问控制策略 NGFW下一代防火墙 AAA 鉴别方式——认证 用户认证的分类&#xff1a; 上网用户上线流程&#xff1a; 二、用户认证实验&#xff1a; 实验拓扑 先配置防…

pmp考试是什么?

PMP是一个全球资格认证&#xff0c;也是目前项目管理领域大家公认的证书&#xff0c;相当于项目管理的入门证书。 一、PMP 是什么 pmp 中文叫项目管理专业人士资格认证&#xff0c;目前项目管理领域大家公认的证书&#xff0c;是一个用来评估项目管理人员的知识技能是否已经达…

D. Divide and Summarize(BFS+二分+预处理)

Problem - 1461D - Codeforces 迈克收到一个长度为n的数组作为生日礼物&#xff0c;决定测试一下它的漂亮程度。 如果有一种方法可以通过一定数量&#xff08;可能是零&#xff09;的切片操作得到一个元素总和为si的数组&#xff0c;那么这个数组将通过第i次漂亮度测试。 一个…

红红火火的VB,悄然离去,新型中文编程,如日中天

“悲哀&#xff01;现在用VB连1200都赚不到。”一位VB程序员有感而发。曾经红红火火的VB编程语言&#xff0c;如今却徘徊在被淘汰边缘&#xff0c;让人惋惜。 依稀记得&#xff0c;读大学时候&#xff0c;有一位财务专业同学&#xff0c;特别喜欢计算机&#xff0c;有空就自学V…

密码学引论 | DES

文章目录DES算法1 算法流程2 算法细节&#xff08;1&#xff09;子密钥的产生&#xff08;2&#xff09;初始置换IPIPIP&#xff08;3&#xff09;加密函数&#xff08;4&#xff09;逆初始置换IP−1IP^{-1}IP−1例题DES算法 1 算法流程 64位密钥经子密钥产生算法产生出16个子…

用 TypeScript 类型运算实现一个五子棋游戏

之前有看到有大佬用类型运算实现了中国象棋程序 和 Lisp 解释器 甚是震惊&#xff0c;之前不太会看不懂。 最近也学了点类型体操的内容想着自己也玩一下。选择五子棋的原因是相对来说规则是更简单一些的也比较好实现。此实现没有考虑性能上优化和最佳实现方式只关注功能的实现…

详细步骤讲解matlab代码通过Coder编译为c++并用vs2019调用

项目上需要C&#xff0c;奈何本人不会&#xff0c;所以就用matlab写好测试后&#xff0c;用matlab Coder编译为c并用vs2019调用 一个简单的例子&#xff0c;求取两个4*4矩阵相加后&#xff0c;在求取最大值与最小值。matlab代码如下 function [a,b] min_max(m,n)temp mn;a m…

STM32F407 电机编码器测量

文章目录一、STM32F407 定时器编码器功能1.1 STM32定时器简介1.2 STM32定时器编码器功能二、带编码器的直流电机三、代码与验证3.1 初始化代码3.2 验证一、STM32F407 定时器编码器功能 1.1 STM32定时器简介 STM32的定时器功能非常强大&#xff0c;根据官方手册&#xff0c;定…

旅游网页设计 web前端大作业 全球旅游私人订制 旅游公司网站模板(HTML+CSS+JavaScript)

&#x1f468;‍&#x1f393;学生HTML静态网页基础水平制作&#x1f469;‍&#x1f393;&#xff0c;页面排版干净简洁。使用HTMLCSS页面布局设计,web大学生网页设计作业源码&#xff0c;这是一个不错的旅游网页制作&#xff0c;画面精明&#xff0c;排版整洁&#xff0c;内容…

OctaneRender界面布局自定义界面教程丨使用教程

您可以通过单击并拖动每个窗格左上角的方块&#xff08;图 1&#xff09;来重新排列 OctaneRender 界面&#xff08;图形编辑器、渲染视口、节点检查器和大纲视图&#xff09;中每个窗格的窗口。 图 1&#xff1a;窗格排列图标 用任何鼠标按钮单击同一个方块会显示更多用于…