Tomcat最基本的五脏六腑与经络运行

一、一个URL如何找到对应的web应用程序

域名:是协议名之后,端口号之前的内容,且仅仅是在网址栏输入的字符串表示。其经过DNS解析能且只能获取ip地址的信息。

主机头: HTTP请求报头Host的值,包含域名和端口号,也仅仅是在网址栏输入的字符串表示。

一个域名只能对应一个ip地址,而一个ip地址可以对应多个域名。每个域名对应的主机头肯定是不相同的,借此便可以区分各个域名对应的web应用程序。

如何根据一个URL找到对应的服务?
输入一个URL后:(1)URL中的域名经过DNS解析得到ip地址,进而找到服务所在的电脑。(2)再根据URL中的端口号找到服务所在电脑的监听端口。此时已经可以确定该访问哪个tomcat服务了,但同一个tomcat服务可以含有多个虚拟主机,且同一个虚拟主机又可含有多个context,只有具体到context才能确定需要访问的web应用程序。(3)再根据URL中隐含的的主机头信息找到需要访问的tomcat服务中的虚拟主机。(4)最后通过URL的上下文根路径确定到需要访问的web应用程序。(5)然后就是根据剩余的URL路径找到该web应用程序下具体的静态资源或servlet,并可以带上额外的查询字符串和请求体。

上述过程中的其他情况:(1)若URL中直接给出了ip地址,则跳过DNS解析。(2)若URL中没有给出端口号,则访问默认的端口80。(3)若URL中的主机头在tomcat中没有配置,但tomcat配置了默认的虚拟主机,则会访问该虚拟主机。这就是为什么有些网站输入域名可以访问,而输入ip就不可访问,也许就是它没有设置默认的虚拟主机。(4)若URL中的上下文根路径在tomcat中没有配置,则访问默认的web应用程序ROOT。

综上可见,区分互联网上不同的web应用程序有四个基本指标:ip地址、端口号、主机头、上下文根路径。通过ip地址和端口号确定一个具体的tomcat,通过主机头和上下文根路径确定该tomcat中一个具体的web应用程序。

二、server.xml中窥tomcat的架构

2.1、一个遵循HTTP协议的服务器需要有哪些组件

第一节所说的规则,都是HTTP协议里的,而tomcat作为一个遵循HTTP协议实现的服务器端软件,自然实现了上述需求:比如对HTTP请求报头host的理解和操作,对上下文根目录的理解和操作。这些也体现在tomcat自身的体系结构中。

tomcat本身也是由一系列可配置的组件构成的,这些组件的作用和相互之间的关系是tomcat的实现上述需求的核心。如果不想涉及其源码的话,我们可以通过server.xml这个tomcat的配置文件,窥探一二。

server.xml中各组件的层级结构:
—— server
———— service【可以多个,用于配置不同的监听端口】
—————— connector(有HTTP连接器和AJP连接器)
—————— engine(一个service下只能有一个)
———————— host【可以多个,用于配置该端口下的不同虚拟主机】
—————————— context【可以多个,用于配置该虚拟主机中不同的web应用程序】

(1) server组件是整个配置文件的根,代表整个容器。
(2) service组件表示一个或多个connector组件的联合,且这些组件共享一个engine组件来处理请求的到来。
(3) connector组件处理与外部的通信,接收外部请求,并向外部返回响应。其又分两种:

  • HTTP连接器——处理与客户端的通信,负责接收客户端的请求,以及向客户端返回响应。其常用属性如下:

    1. port:服务器端套接字监听的TCP端口号,默认为“8080”
    2. maxPostSize:容器接收的Post请求的最大尺寸,默认为“2097152”
    3. redirectPort:可以将SSL请求重定向到该属性指定的端口,若不设置该属性,则不会进行重定向
    4. URIEncoding:指定用于解码URI字节的字符编码,Tomcat7对URI默认编码是ISO-8859-1,Tomcat8对URI默认编码是UTF-8
    5. useBodyEncodingForURI:指示是否使用在ContentType中指定的编码来取代URIEncoding。【此处有个概念需要明确,ContentType是实体报头,其指定的编码是只针对请求体和响应体的,而不会影响URI,像request.setCharacterEncoding(“UTF-8”),也只会对请求体进行UTF-8解码】
    6. acceptCount:在队列中排队的连接请求的最大数目,超过该数目后任何接收到的请求都将被拒绝,默认为“10”
    7. maxThreads:线程池中请求处理线程的最大数目,默认为“200”
    8. maxSpareThreads:线程池中空闲线程的最大数目,默认为“50”
    9. minSpareThreads:线程池中空闲线程的最小数目,默认为“4”
  • AJP连接器——处理与其它HTTP服务器的通信。tomcat服务器擅长处理动态页面,而apache服务器擅长处理静态页面,通过AJP连接器可实现tomcat与apache服务器间的通信。当apache服务器接收到动态内容请求后可将请求转发给tomcat服务器的AJP连接器。(现已不流行这样做了,而是将所有静态资源置于apache服务器上,将servlet/jsp置于tomcat中,客户端直接分别请求两个服务器)

(4)engine组件处理connector接收到的请求,并想connector传送完整的响应。常用属性如下:

  1. defaultHost:默认主机名,必须和其下的一个host的name属性相匹配。该属性是必须的,这也就意味着通过ip地址直接访问tomcat服务中的应用程序,一定会访问到一个。某些服务器该属性不是必须的。

(5)host组件表示一个虚拟主机,对应一个http请求报头Host的值。其常用属性如下:

  1. name:虚拟主机的网络名,非默认主机名要和http请求报头Host的值相对应
  2. appBase:指定虚拟主机的应用程序基目录,可以是相对路径或绝对路径,你所常见的tomcat中的webapps文件夹就是它
  3. autoDeploy:当tomcat正在运行时,若有新的web应用程序加入到appBase指定的目录下,是否会自动部署该应用程序。 默认为“true”
  4. deployOnStartup:当tomcat启动时,是否自动部署在appBase指定目录下的所有web应用程序。 默认为“true”

(6)context组件表示一个虚拟主机中的一个web应用程序。其常用属性如下:

  1. cookies:是否将cookie应用于session。 默认为“true
  2. docBase:类似host的appBase,指定web应用程序的文档基目录,可以是相对路径或绝对路
  3. path:指定web应用程序的上下文根路径
  4. reloadable:当tomcat运行时,若有类文件更新,则会重新加载该web应用程序。默认为“false”

    这里写图片描述
    ——摘自《Servlet/JSP深入详解》

2.2、这么多嵌套的组件,如何管理?

(1)组件的生命周期:

一个tomcat要能真正启动起来为各客户端服务,其中所有的组件都要启动起来才可以,而若一个tomcat需要关闭,其中的所有组件也都要关闭并释放内存——这就涉及到各组件的生命周期的管理问题。

对于该问题,tomcat的设计用到了观察者模式,tomcat的各组件都实现了Lifecycle接口,实现了该接口的组件可以被它的父组件控制,这样一层一层地直到最高级的server组件,如此就可以控制所有组件的生命周期了。

通常我们要启动一个tomcat,只要直接运行其startup命令即可。
(1)执行该命令本质上是在执行server组件的start()方法,此时server作为一个subject会通知它的所有Observer即所有service组件。
(2)所有的service得到通知后也会调用它们自己的start()方法。这里的service和后面的engine、host和context不仅具有observer的身份,还具有subject的身份,因为它们不仅会接收父组件的通知,还会向它们的子组件发送通知。
(3)以此类推,所有的engine执行start()→所有的host执行start()→所有的context执行start()→所有的wrapper执行start()。

关闭一个tomcat的过程同理,只不过每个组件调用的都是destroy()方法罢了。

(2)组件的拼装:

噢对了,tomcat在启动时怎么会知道一个组件的子组件是啥呢?以及哪些组件之间才具有Subject和Observer的关系呢?——因为在tomcat启动后,在执行start()前,会先根据server.xml和web.xml中声明的各组件的关系,遍历调用各组件的addChild()方法,将一个个独立的组件拼装成一个树,再根据组件树的层级关系依次注册观察者。

三、最小的请求分发单位——Servlet

tomcat本身已经可以帮你做到把一个请求url定位到茫茫万维网中的某个context,而根据context之后路径的请求分发操作则需要你通过实现一个个servlet来分别接收和处理。

3.1、Wrapper——Servlet的容器

管理servlet的wrapper是tomcat中最底层的组件,其实现类是StandardWrapper,它没有子组件了,每个StandardWrapper负责管理一个servlet,会进行如下操作:【注意加粗字体的概念的区别】
(1)tomcat启动时servlet 类的加载、类的初始化
(2)tomcat启动时或第一次请求到来时servlet 类的实例化
(3)servlet类的实例化后紧接着调用servlet对象的init(ServletConfig config)方法进行 对象的初始化
(3)调用serlvet对象的service()方法对请求进行处理
(4)调用servlet对象的destroy()方法结束该servlet对象的生命周期

所以说StandardWrapper组件就是servlet的最直接的容器,管理着servlet的生命周期、着实的去使用你所实现的servlet。你所实现的servlet的init()、service()和destroy()方法都是给StandardWrapper去调用的。

另外,serlvet的init()方法的入参——ServletConfig对象,也是由StandardWrapper传递的。实际上,StandardWrapper传递给init()方法的是实现了ServletCofig接口的StandardWrapperFacade对象【这里用到了门面设计模式】,servlet可以获得的信息都封装在StandardWrapperFacade对象中。

3.2、Servlet的接口体系

这里写图片描述
——摘自《Servlet/JSP深入详解》

Servlet的体系结构就是以Servlet为核心处理器,而ServletConfig、ServletRequest和ServletResponse就是需要传递给Servlet进行处理的两类信息包装。其中:

  1. ServletConfig包含了Servlet容器的信息,通过该对象可获得ServletContext对象从而与容器进行通信。在servlet进行init()时从容器中获取的servlet的初始化信息也保存在该对象中。
  2. ServletRequest和ServletResponse则包含了来自客户端的请求信息和返回给客户端的响应信息。【tomcat的各组件的生命周期都是伴随着tomcat的启动和关闭,而这两个对象的生命周期则是伴随着一次请求的到来与返回】

(1)Servlet接口

  • init(ServletConfig cfg)、destroy() 管理servlet的生命周期
  • service(ServletRequest req, ServletResponse res) 处理来自客户端的信息
  • getServletConfig() 获取来自容器的信息

(2)ServletConfig接口

  • getServletContext() 获取Context容器的信息
  • getInitParameterNames()、getInitParameter() 获取servlet的初始化信息

(3)ServletRequest、ServletResponse接口

  • getCharacterEncoding() 、setCharacterEncoding() 获取请求正文使用的字符编码、设置响应的字符编码
  • getContentType()、getContentType() 获取请求正文的MIME类型、设置响应的MIME类型
  • getParameterNames()、getParameter()、getParameterValues() 获取请求中的参数和参数的值
  • getInputStream()、getReader()、getOutputStream()、getWriter() 获取请求正文、发送响应

3.3、Servlet的一个具体实现——HttpServlet

servlet规范是javaee规范的重要组成部分,规范协调了WEB服务器开发者与WEB应用业务逻辑开发者的工作,两者的工作若按照规范去做,相互的工作就会无缝地衔接在一起。但servlet规范终究只是一组接口,需要具体的实现。

(1)GenericServlet抽象类

  • 最大的特点就是该类同时实现了Servlet、ServletConfig和Serializable接口,可以直接继承该类并重写service()方法即可

(2)HttpServlet实现类

  • 继承于GenericServlet,在service()方法中将入参ServletRequest和ServletResponse转换为HttpServletRequest和HttpServletResponse。并且在service()方法体中会调用HttpServletRequest的getMethod()来判断该Http的请求方式,再去调用相应的doXxx()方法。故而可直接继承该类并只需重写相应的doXxx()方法

(3)HttpServletRequest、HttpServletResponse接口

  • getRequestURL()、getRequestURI()、getContextPath()、getQueryString() 获取URL中的各种信息
  • getMethod() 获取Http的请求方式
  • getHeaderNames()、getHeader()、getHeaders()、addHeader()、setHeader() 获取请求报头、设置响应报头
  • getCookies()、addCookie()、getSession(),getSession(boolean create) 获取和设置cookie与session
  • sendRedirect() 发送一个重定向响应到客户端,会设置响应报头Location
  • setStatus() 设置响应的状态码
  • encodeURL() 使用seesion ID对指定的url进行编码

四、总结

前三章简单述说了一个经典的HTTP服务器——Tomcat,所具备的最最基本的组件、功能和架构体系。上面所说的几点在理论上已经足以实现一个最最基本的web服务了,其他的诸如监听器、过滤器、JSP技术等是对tomcat基本架构体系的补充和增强。

需要明确的一点:无论是tomcat的架构体系,还是servlet规范的设计,其最上层的真正指导方针是HTTP协议。tomcat的这种架构设计是为了满足HTTP协议的需求,他的service、host、context组件本质上就是为了对一次HTTP请求的各部分进行正确的解析和处理。而servlet,一个个相互独立的存在,是不是就是HTTP请求无状态特点的体现呢?我们还可以有更多的思考和回味。

参考资料

参考书籍:

《Servlet/JSP深入详解》——孙鑫

《深入分析Java Web技术内幕》——许令波
第9章 Servlet工作原理解析
第11章 Tomcat的系统架构与设计模式

参考链接:

听说一个IP可以绑定多个域名,那么服务端是怎么实现的?
Web.xml详解
servlet 获取 post body 体 (用流读取为空的问题)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值