Tomcat架构解析之AJP

一、前言

    除了HTTP,Tomcat还支持AJP协议,以便于Apache HTTP Server等Web服务器集成,这篇博客主要讲解AJP协议的基础知识以及其配置使用方式。

二、基础知识

    为了满足负载均衡、静态资源优化、遗留系统集成(如集成PHP Web应用)等应用部署要求,我们习惯于在Servlet容器的前端部署专门的Web服务器,如Apache HTTP Server。由于增加了Web服务器的请求转发处理,虽然此时的单独请求性能必定低于直链Servlet容器的情况(增加了一层请求转发),但是整体性能却要大大好于直链。这主要有两个原因:(1)通过Web服务器的负载均衡机制,可以大大降低单台服务器的负载,从而提升请求处理效率;(2)充分利用Web服务器在静态资源处理上的性能优势,提升Web应用静态资源处理速度(尤其对于静态资源较多的Web应用)。

    因此出于性能方面的考虑,要尽可能提高Web服务器于Servlet容器之间数据传输的效率,以减少由此带来的额外开销。服务器在传输可读性文本时,一种更好的方式是采用二进制格式进行传输,这样会大大降低每次请求发送的数据包的大小。此外,由于Web服务器与Servlet容器通过TCP链接,在多个请求/响应周期组件复用同一个链接。

    只要链接被分配给一个特定的请求,他将不会被用于其他请求,知道请求处理周期终止。换句话说,请求不能通过链接实现多路复用(这简化了链接两端的编码工作,但是也会导致需要同时打开更多的链接)。

    AJP(Apache JServ Protocol)便是按照以上思想设计实现的一种通信协议。采用AJP协议时,客户端与服务器的交互如下图所示:
在这里插入图片描述
    AJP时Alexei Kosut创建的定向包(packet0-oriented)通信协议,采用二进制格式传输可读文本。从该协议的命名不难看出,其最初的目的便是用于Apache HTTP Server(Web服务器)与Apache Jserv之间的通信,以提示Web服务器与应用服务器集成时的通信性能。

    在AJP协议下,Web服务器打开一个与Servlet日期的链接后(如HTTP头)将会以高度精简的格式通过链接发送。如果存在请求体,他将会以单独的包紧随其后进行发送。

    1.AJP消息包结构
    AJP消息包分为请求消息(由Web服务器发送到Servlet容器)和响应消息(由Servlet容器发送到Web服务器),消息包最大为8KB。

    请求消息的格式如下图所示:
在这里插入图片描述
    请求消息的前两个字节固定为0x1234,第二个和第三个字节用于记录有效载荷(playload,即具体的请求消数据,包括请求方法、地址、请求头、请求体等),一次理论上每条消息的有效载荷上限为2的16次方,但是实际上最大只能8KB。从第4个字节开始为具体的有效载荷。

    响应消息的格式如下图所示:
在这里插入图片描述
    响应消息的前两个总结固定为AB两个字母的ASCII码,其余部分与请求消息相同。

    绝大多数请求、响应消息的有效载荷首字节均记录了消息类型的编码,只有一个例外,就是发送请求体数据的消息,他没有记录相关信息。

    AJP支持的请求及响应消息类型如下图所示:
在这里插入图片描述
    出于安全考虑,对于Shutdown请求,Servlet容器只有在请求来自其部署的机器时才会实际执行关闭操作。

    Web服务器在发送Forward Request后会立刻发送一个Data包。

    基于上诉消息类型,在一次HTTP请求过程中,Web服务器与Servlet容器交互如下图所示:
在这里插入图片描述
    在AJP协议中,每种请求/响应消息拥有不同的结构,我们以Forward Request请求和SendHeaders、Send Body Chunk响应进行说明。

    2.Forward Request结构
    Forward Request请求消息结构如下图所示:
在这里插入图片描述
    首字节(prefix_code)表示消息类型编码,一次对于Forward Request来说,改字节固定为“ 2 ”。

    第二个字节(method)表示HTTP请求Method的编码,具体如下表所示:
在这里插入图片描述
    protocol(请求协议),req_uri(请求URI)、remote_addr(远程地址)、remote_host(远程主机)、server_name(服务器名称)、server_port(服务器端口号)、is_ssl(是否为SSL),这些属性均是自描述,不再详细说明。

    请求头由两个属性描述,首先num_headers存储请求头中信息的数目。其次request_headers存储具体的头信息的键值对。通用的头名称以整数编码形式存储,非通用头名称,以字符串形式存储。值为普通的字符串,紧随请求头名称之后。

    通用头名称编码如下表所示:
在这里插入图片描述
    Servlet容器解析时,可以先尝试读取前两个字节,如果第一个字节为“ 0xA0 ”,那么前两个字节即为请求头名称的编码(如果将所有的请求头按照编码顺序存储到一个数组中,第二个字节可以直接作为请求头名称在数组中的位置,以简化查询)。否则前两个字节为头名称字符串长度,并继续读取后续字节作为请求头名称。

    当然,这种设计基于的前提是请求头名称的长度不会超过0x9999(0xA000-1),虽然有些随意,但是却是合理的。

    请求属性由attribute描述(因为Forward Request不包含请求体数据,所有不需要额外字节来描述attribute的数目,请求头之后的所有数据均可认为是attribute)。

    与请求头类似,请求属性也以键值对的形式存储。键为属性编码,除are_done外,其他属性值均为字符串。are_done没有对应的属性值,其编码为特殊字符,用以表示attribute以及当前请求包的结束。

    当前支持的请求属性编码如下表所示:
在这里插入图片描述
    在具体实现中,context、servlet_path两个属性并未使用,不再详细说明。

    remote_user、auth_type用于HTTP请求认证。

    query_string、ssl_cert、ssl_cipher、ssl_session分别对应于HTTP请求中相应的内容,如果你了解HTTP协议,那么这些具体含义不难理解。

    jvm)remote用于支持粘性会话主要用于集群,req_attribute用于支持扩展请求属性的发送,所有扩展属性和属性值以一个字符串的形式存储到req_attribute的值中。

    3.Send Headers结构
    Send Headers结构相应消息包结构如下所示:
在这里插入图片描述
    prefix_code固定为4,表示响应消息类型为“ Send Headers ”。http_status_code和http_statis_msg表示HTTP响应码和描述。

    响应头的结构与Formward Request 中的请求头结构相同,支持的响应头编码如下所示:
在这里插入图片描述

    4.Send Body Chunk结构
    Send Body Chunk响应消息包结构如下所示:
在这里插入图片描述
    prefix_code固定为3,表示Send Body Chunk响应消息。chunk_length表示响应体数据长度,chunk表示具体的响应体数据。

三、Web服务器组件

    要采用AJP协议进行通信,Web服务器和Servlet容器必须均支持AJP协议菜可以。Tomcat、Jetty等Servlet容器均已支持AJp协议,而Web服务器端扫有不同。

    首先,Tomcat提供了子项目Tomcat COnnectors用于为有限的几款Web服务器提供AJP协议模块,我们只要在Web服务器端部署对应的模块,Web服务器便可以支持AJP协议。

    1.Tomcat Connectors
    Tomcat Connectors 提供了3款Web服务器的AJp协议模块。分别为Apache HTTP Server、IIS、SunOne。

    【1】Apache HTTP Server
    Tomcat Connectors提供了一个Apache模块的mod_jk,用于AJP协议处理。

    模块文件视操作系统而定,可能mod_jk.so、mod_jk.nlm或者MOD_JK.SRVPGM以及一个配置文件workers.properties(用于配置Tomcat示例的主机及端口)。

    【2】IIS
    Tomcat Connectors 提供了一个IIS定向器插件JK ISAPI,用于AJP协议处理。JK ISAPI主要包含过滤器和扩展两部分功能,其具体工作方式如下:

  • IIS启动时加载JK ISAPI定向器插件,并且对每个来自客户端的请求执行其过滤器功能。
  • 过滤器根据uriworkermap.properties文件中配置的URI信息检测当前请求的URL,如果存在匹配的URI,则将请求交由JK ISAPI扩展处理。
  • JK ISAPI扩展收集请求参数,通过AJP协议将其转发到合适的Servlet容器。
  • JK ISAPI扩展收集来自Servlet容器的响应,并且将其转换为HTTP协议返回浏览器。

    【2】SunONE
    SunONE Web服务器(即以前的Netscape Web服务器、iPlanet Web服务器)拥有自己的Servlet容器,但是我们仍然可以通过配置将Servlet请求定向到Tomcat。Tomcat Connectors 通过NSAPI定向器插件来实现该功能。

    其具体工作方式如下:

  1. NSAPI定向器是一个Netscape服务步骤插件,Netscape加载NSAPI定向器插件并对分配到servlet配置对象的请求调用服务处理功能。
  2. 对于每个来自客户端的请求,Netscape执行一组Name Trans指令(在obj.conf文件中添加),其中一类指令为assign-name,用于检测URL是否匹配。
  3. 如果匹配,assign-name将为请求分配Servlet命名对象 ,这会使Netscape将请求发送到servlet配置对象。
  4. 此时会执行插件提供的jk_service扩展。该扩展收集请求参数,通过AJP协议将其转发到合适的Servlet容器。
  5. ik_setvice扩展收集来自Servlet容器的响应,并且将其转换为HTTP协议返回浏览器。

    2.mod_proxy_ajp
    在Apache HTTP Server 2.1及其以后版本中,可以通过两种方式支持AJP协议,一种是使用前面讲到的mod_jk,另一种是使用mod_proxy和mod_proxy_ajp。

    mod_proxy是Apache 的代理模块,他与其他代理模块(mod_proxy_http、mod_proxy_ftp、mod_proxy_ajp)配合以实现对具体协议的代理功能。因此,如果我们要使用Apache 代理AJP协议,必须确保mod_proxy和mod_proxy_ajp模块同时加载。

    mod_proxy和mod_proxy_ajp已默认包含在Apache服务器发布包中(是Apache HTTP Server项目的一部分),因此只需要修改配置文件以确保在启动时加载即可。

    mod_proxy和mod_proxy_ajp的工作过程与mod_jk类似。

    3.nginx_ajp_module
    Nginx服务器可以通过第三方插件nginx_ajp_module支持AJP协议。由于该项目资料较少,不再详细说明,具体使用方式可参见项目官方地址。

四、配置方式

    Tomcat在默认情况下即支持AJP/1.3,不需要我们另外配置。我们可以在$ CATALINA_BASE/conf/server.xml中找到相关内容,如下所示:
在这里插入图片描述
    从配置上来看,AJP请求的处理端口为8009,我们可以通过修改port属性,将其修改为希望分配的端口。

    其次,属性为protocol为“AJP/1.3”表示当前链接器支持的协议为AJP/1.3。与HTTP协议配置类似,采用此种方式,Tomcat会自动检测当前服务器是否安装了APR。如果安装了APR,那么Tomcat将自动使用APR处理(AJP),否则使用NIO。初次之外,我们还可以明确指定协议处理类,此时Tomcat的检测将不再生效。
    如下所示,指定采用NIO处理AJP请求:
在这里插入图片描述

发布了185 篇原创文章 · 获赞 457 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/weixin_42146366/article/details/98209292