Tomcat是如何处理请求的(上)

Tomcat历史悠久,从正式发布到现在已经20多年了,但是就算在现在仍然占有一席之地,Spring Boot目前很火,而Tomcat则是它默认的web服务器实现,那么Tomcat为H什么这么重要,接下来我们来分析一下Tomcat是如何接收,并处理一个请求的?

大家都知道,Tomcat启动时需要绑定一个端口,默认为8080,一旦Tomcat启动成功就占用了机器的8080端口,也就是表示,机器中8080端口接收到的数据会交给Tomcat来处理,机器接收到的数据肯定是字节流,那么Tomcat是如何解析这些字节流的?解析为什么对象?

我们知道,Tomcat是一个Servlet容器,虽然我们在使用Tomcat时,会把整个项目打成一个wa包部署到Tomcat里去,整个项目中包括了很多类,但是对于Tomcat而言,它只关心项目中的那些实现了Servlet接口的类,或者说只关心在web.xml中或通过@WebServlet注解所定义的Servlet类。

比如,我们把一个项目部署到Tomcat的webapps文件夹里后,Tomcat启动时会去找该项目文件夹中的/WEB-NF/web.xml文件,并解析它,解析完了之后,就知道当前项目中定义了哪些Servlet,并且这些Servlet对应接收什么格式的请求(servlet-mapping)

<servlet>
    <servlet-name>testServlet</servlet-name>
    <servlet-class>com.test.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>testServlet</servlet-name>
    <url-pattern>/test</url-pattern>
</servlet-mapping>

Tomcat启动后,在接收到某个请求后,就可以根据请求路径和ur-pattern进行匹配,如果匹配成功,则会把这个请求交给对应的servlet进行处理.

在这里插入图片描述

上面只是请求处理的一个大概流程梳理,这其中还有很多细节需要我们挖掘,比如:

1.Tomcat是如何解析字节流的?解析为什么对象?

2.我们定义Servlet时会去定义doGet、doPost等方法,那么Tomcat是如何判断该调哪个方法,并且是如何调用的?

3.其他问题

我来看一下我们定义的Servlet:

public class ServletDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.getWriter().print("hello world");
    }
}

上面代码中,我们定义了一个doGet(HttpServletRequest req,HttpServletResponse resp)方法,并且有两个参数,一个代表请求,一个代表响应,我们知道doGet方法就是在接收到get请求时会被调用,事实上,Tomcat接收到一个请求(字节流)后,就会将字节流解析为一个HttpServletRequest对象,然后根据请求找到对应的Servlet,然后调用Servlet中的service()方法,service()方法在我们定义的Servlet的父类HttpServlet中,而在service方法中,会去判断当前请求方法,如果是get请求,就会调用doGet,如果是post,就会调用doPost,service方法实现如下:

    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                } catch (IllegalArgumentException var9) {
                    ifModifiedSince = -1L;
                }

                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }

那么,Tomcat接收到数据并解析为HttpServletRequest对象后,就直接把请求交给Servlet了吗?

并不是,Tomcat还有其他考虑,比如:

1.假如,现在有一段逻辑,想让多个Servlet公用,就像切面一下,我们希望在请求被这些Servlet处理之前,能先执行公共逻辑。

2.假如,现在有一段逻辑,想让多个应用内的Servlet公用,就像切面一下,我们希望在请求被这些应用处理之前,能先执行公共逻辑。

3.我们知道,不同的域名可以对应同一个P地址,对应同一个机器,那么我希望Tomcat能根据不同的域名做不同的处理

4.如果Tomcat支持多域名,那么我希望能使得这多个域名能共享某段逻辑

可能上面这四种假设,大家会有点懵,没关系,请直接往下面看,在Tomcat中存在四大Servlet容器:

1.Engine:直接理解为一个Tomcat即可,一个Tomcat—个Engine

2.Host:一个Host表示一个虚拟服务器,可以给每个Host配置一个域名

3.Context:一个Context就是一个应用,一个项目

4.Wrapper:一个Wrapper表示一个servlet的包装,Wrapper在后文详解

并且这四个Servlet容器是具有层次关系的:一个Engine下可以有多个Host,一个Host下可以有多个Context,一个Context下可以有多个Wrapper,一个Wrapper 下可以有多个servlet实例对象。

Tomcat接收到某个请求后,首先会判断该请求的域名,根据域名找到对应的Host对象,Host对象再根据请求信息找到请求所要访问的应用,也就是找到一个Context对象,Context对象拿到请求后,会根据请求信息找到对应的Servlet,那么Wrapper是什么?

我们在定义一个Servlet时,如果额外实现了SingleThreadModel接口,那么就表示该Servlet是单线程模式:

1.定义Servlet时如果没有实现SingleThreadModel接口,那么在Tomcat中只会产生该Servlet的一个实例对象,如果多个请求同时访问该Servlet,那么这多个请求线程访问的是同一个Servlet对象,所以是并发不安全的

2.定义Servlet时如果实现了SingleThreadModel接口,那么在Tomcat中可能会产生多个该SServlet的实例对象,多个请求同时访问该SServlet,那么每个请求线程会有一个单独的Servlet对象,所以是并发安全的

所以,我们发现,我们定义的某个Servlet,在Tomcat中可能会存在多个该类型的实例对象,所以Tomcat需要再抽象出来一层,这一层就是Wrapper,一个Wrapper对应一个Servlet类型,包装器中有一个集合,用来存储该包装器对应的Servlet类型的实例对象。

所以一个Context表示一个应用,如果一个应用中定义了10个Serwlet,那么Context下面就有10个Wrapper对象,而每个wrapper中可能又会存在多个Servlet对象

在这里插入图片描述

还有一点,在这个四个容器内部,有一个组件叫做Pipeline,也就管道,每个管道中可以设置多个Valve,也就阀门。

管道与阀门的作用是,每个容器在接收到清求时会先把请求交给容器中的每个阀门处理,所有阀门都处理完了之后,在会将请求交给下层容器,通过这种机制,就解决了上面所提到的四种假设:

1.Engine:可以处理Tomcat所接收到所有请求,不管这些请求是请求哪个应用或哪个Servlet的。

2.Host:可以处理某个特定域名的所有请求

3.Context:可以处理某个应用的所有请求

4.Wrapper:可以处理某个servlet的所有请求

值得一说的是,Wrapper还会负责调用Servlet对象的service()方法。

到此,Tomcat接收到字节流并解析为HttpServletRequest对象之后,HttpServletRequest对象是如何流转的给大家分析完了,总结一下就是:

在这里插入图片描述

接下来再来给大家分析一下,Tomcat是如何将字节流解析为HttpServletRequest对象的,这个问题其实不难,只要想到这些字节流是谁发过来的?

比如浏览器,而浏览器在发送数据时,会先将数据按HTP协议的格式进行包装,在把HTP数据包通过TCP协议发送出去,Tomcat就是从TCP协议中接收到数据的,只是从TCP协议中接收的数据是字节流,接下来要做的就是同样按照HTTP协议进行解析,比如解析出请求行、请求头等,从而就可以生成HttpServletRequest对象。不过,整个解析过程还是比较复杂的,包括Tomcat底层的NIo模型、BIo模型、线程模型的实现也是比较复杂的,后面会介绍。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值