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

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 键值对

例9:网页登录

在这里插入图片描述

约定前后端交互接口
两组交互:登录+获取页面

在这里插入图片描述

在这里插入图片描述

整体有三步骤

1.先编写一个简单的登录页面,使用form表单来构造post请求

这个login.html页面需要放在webapp下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="login" method="post">
        <input type="text" name="username">
        <input type="password" name="password">
        <input type="submit" value="登录">
    </form>
</body>
</html>

2.编写一个Servlet来处理登录请求

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //处理用户请求
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        //判定用户名和密码是否正确
        //正常来说数据都是放在数据库中进行操作的
        //此处为了简单,就假设数据库中有用户名"fl"和对应的密码"123"
        //这么写防止null异常
        if("fl".equals(username) && "123".equals(password)) {
            //登录成功

            //创建好一个Session,并且在Session中填写一些身份信息,以供后面的逻辑使用
            HttpSession httpSession = req.getSession(true);
            //向会话中存储用户身份
            httpSession.setAttribute("username", username);
            //向会话中存储用户访问次数
            httpSession.setAttribute("count", 0);
            //重定向
            resp.sendRedirect("index");
        } else{
            //登录失败
            resp.getWriter().write("login failed");
        }
    }
}

3.编写一个Servlet来处理服务器端返回主页的逻辑

@WebServlet("/index")
public class IndexServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    	//返回一个页面(简单的html页面)
    	
    	//这里的HTTPSession对象和登录时的HTTPSession对象是同一个对象,
    	//登录时就已经创建了,这里就不需要重复创建,参数给为false即可
    	//因此,根据相同的key,拿到的value也一定是相同的
        HttpSession session = req.getSession(false);
        //返回一个页面(简单的html页面)
        //此处需要得到用户名和访问次数,因为返回值类型是Object,因此需要类型转换
        String username = (String) session.getAttribute("username");
        Integer count = (Integer) session.getAttribute("count");
        //访问次数加1,并修改会话中的count
        count += 1;
        session.setAttribute("count", count);
        resp.setContentType("text/html;charset=utf8");
        resp.getWriter().write("<h3> 名字:"+ username +" 次数:"+ count +"</h3>");
    }
}

运行Tomcat,通过Fiddler抓取客户端和服务器的三次交互过程

第一次交互:浏览器从服务器上拿到登录页面

在这里插入图片描述

第二次交互:点击登录之后,就要给服务器发送一个登录请求,服务器会返回响应

在这里插入图片描述

第三次交互:浏览器收到302响应之后,再次向服务器发起请求,访问主页

在这里插入图片描述

最终的页面结果

在这里插入图片描述

例10:上传文件,请求是这种格式 form-data,服务器通过Post获取参数

上传文件也是开发中的一个典型需求。上传文件的时候,在前端需要用到form表单,form表单中需要使用特殊的类型 form-data

此时提交文件的时候,浏览器就会把文件内容以form-data的格式构造到HTTP请求中。服务器就可以通过getPart来获取参数

一个HTTP请求,可以一次性的提交多个文件的,每个文件都称为一个Part,每个Part都有一个name(身份标识),服务器代码中就可以根据name找到对应的Part

HttpServletRequest 类中关于Part的方法

方法名称描述
Part getPart(String name)获取请求中给定 name 的文件
Collection<Part> getParts()获取所有的文件

Part方法

方法名称描述
String getSubmittedFileName()获取提交的文件名
String getContentType()获取提交的文件类型
long getSize()获取文件的大小
void write(String path)把提交的文件数据写入磁盘文件

提交页面

该页面也要放在webapp下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="upload" method="post" enctype="multipart/form-data">
        <input type="file" name="MyImage">
        <input type="submit" value="提交">
    </form>
</body>
</html>

form表单中参数enctype="multipart/form-data不能缺

处理文件上传请求

//要给类加上这个@MultipartConfig注解,来开启对于上传文件的支持
//否则getPart调用的时候就会抛出异常
@MultipartConfig
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Part part =  req.getPart("MyImage");
        System.out.println(part.getSubmittedFileName());//文件名
        System.out.println(part.getContentType());//文件类型
        System.out.println(part.getSize());//文件大小
        part.write("D:/aaa.png");//把上传过来的文件(上传图片)放到D盘中,并取名为aaa.png
        resp.setContentType("text/html;charset=utf8");
        resp.getWriter().write("上传成功");
    }
}

运行Tomcat

在这里插入图片描述

随机选择一张图片,并提交上传

在这里插入图片描述

在这里插入图片描述

通过Fiddler抓包,查看请求

在这里插入图片描述

在这个boundary中间的部分,就是图片的二进制数据(二进制数据在记事本中打开就是乱码)

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值