Tomcat6.0源码学习--Connector架构

概述

Tomcat虽然具有传统Web服务器(如Apache)的功能(处理Html页面,css文件等静态资源的能力),但更多的,TomcatServlet容器著称(处理JspServlet等动态资源的应用服务器)。由Tomcat的总体架构可知(参见:Tomcat6.x架构概述),Servlet容器由2个主要组件构成:Connector(连接器)和Container(容器)。Connector负责接收客户端的请求,而Container处理并响应该请求。

       JavaEE规范可知,Servlet容器内部只处理HTTP协议的请求,但是对于连接器的设计来说,它可以接收任何协议(如HTTPAJP等)的请求,因此,在连接器接收到客户端的请求(Socket)后,需要将该请求包装成容器可以处理的对象,然后再传递给容器处理,同时,连接器也要创建一个响应对象一并传给容器,好让容器响应请求。如此一来,容器就与具体传输协议解耦了,而这正是Connector架构所要达到的目的。

Connector架构

基于上面的分析,我们可以将连接器和容器分别抽象为ConnectorContainer接口,将请求和响应抽象为RequestResponse接口。

Connector接口中的重要方法有:

public Request createRequest() ;

public Response createResponse();

public Container getContainer();


 

Tomcat6.x的设计

下面我们看看Tomcat6.x是如何设计的。我们知道,TomcatServlet容器有个好听的名字叫Catalina,在其内部,Catalina容器抽象为org.apache.catalina.Container接口,将连接器抽象为org.apache.catalina.connector.Connector类,请求响应抽象为org.apache.catalina.connector.Requestorg.apache.catalina.connector.Response

org.apache.catalina.connector.Connector类也有以下方法:

public Request createRequest() ;

public Response createResponse();

public Container getContainer();


 

Connector接收到客户端的Socket请求,调用createRequest方法创建请求对象(org.apache.catalina.connector.Request),调用createResponse方法创建响应对象(org.apache.catalina.connector.Response),然后调用getContainer方法获得容器,最后传递Request对象和Response对象并调用Containerinvoke方法处理请求。

 

Connector支持的协议

对于Apache(或其它Web服务器)来说,它可以加载用C语言写的Apache模块(mod_jk , mod_jk2 ,mod_proxy)作为连接器(Connector),这些连接器(Connector)的工作原理一致,可以根据配置支持不同协议的连接器。对于Tomcat来说,其Servlet容器也有用java语言实现的连接器(Connector)模块,这些模块的工作原理也应该一致并且可以根据配置支持不同的协议。为了实现连接器处理工作的一致性和协议的可配置、可扩展性,在Tomcat6.x中,将一致性的工作抽象到org.apache.catalina.connector.Connector类,而将具体协议的差异抽象到org.apache.coyote.ProtocolHandler接口之中。Tomcat6.x通过在配置文件中设置不同的协议类型来初始化不同的ProtocolHandler实例,从而实现协议的可配置、可扩展性

Tomcat6.x中,默认支持2种协议类型的连接器:HTTP/HTTPS协议的HTTP/1.1AJP协议的AJP/1.3Tomcat6.x使用Coyote模块来实现Connector框架,因此这些协议处理器(ProtocolHandler)在包org.apache.coyote下可以找到。

 

CoyoteConnector框架实现的名字,在该模块下提供了2种协议类型的ProtocolHandler

 

如果想扩展连接器,可以在server.xml文件中指定协议和协议处理器的类名。如下:

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

然而,HTTP协议和AJP协议几乎符合所有的需求,因此,我们很少会扩展连接器。

在构造连接器时,连接器根据是否支持Apache Portable Runtime (APR),选择不同的类支持HTTP协议和AJP协议,其对应关系如下:

l         支持APR

HTTP/1.1协议对应org.apache.coyote.http11.Http11AprProtocol

AJP/1.3协议对应org.apache.coyote.ajp.AjpAprProtocol

l         不支持APR

HTTP/1.1协议对应org.apache.coyote.http11.Http11Protocol

AJP/1.3协议对应org.apache.jk.server.JkCoyoteHandler

 

注:APR(Apache portable Run-time librariesApache可移植运行库)的目的如其名称一样,主要为上层的应用程序提供一个可以跨越多操作系统平台使用的底层支持接口库。在早期 Apache版本中,应用程序本身必须能够处理各种具体操作系统平台的细节,并针对不同的平台调用不同的处理函数。启用APR将大大提升Tomcat对静态文件的处理性能。

 

下面看看如何构建不同的连接器:

public Connector(String protocol) throws Exception {

              setProtocol(protocol);

              // Instantiate protocol handler

              try {

                     Class clazz = Class.forName(protocolHandlerClassName);

                     this.protocolHandler = (ProtocolHandler) clazz.newInstance();

              } catch (Exception e) {

                     log.error(sm.getString(

                                   "coyoteConnector.protocolHandlerInstantiationFailed", e));

              }

}

 

public void setProtocol(String protocol) {

              // Test APR support

              initializeAPR();

 

              if (aprInitialized) {

                     if ("HTTP/1.1".equals(protocol)) {

                            setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");

                     } else if ("AJP/1.3".equals(protocol)) {

                            setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");

                     } else if (protocol != null) {

                            setProtocolHandlerClassName(protocol);

                     } else {

                            setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");

                     }

              } else {

                     if ("HTTP/1.1".equals(protocol)) {

                            setProtocolHandlerClassName("org.apache.coyote.http11.Http11Protocol");

                     } else if ("AJP/1.3".equals(protocol)) {

                            setProtocolHandlerClassName("org.apache.jk.server.JkCoyoteHandler");

                     } else if (protocol != null) {

                            setProtocolHandlerClassName(protocol);

                     }

              }

 

}


 

Connector构造函数接收一字符串参数protocol,并根据该字符串加载不同的ProtocolHandler

 

ProtocolHandler

每个ProtocolHandler代表着一种协议的支持,如Tomcat6.x默认支持的协议http1.1ajp,相应的处理器为:Http11AprProtocolAjpAprProtocolHttp11ProtocolAjpProtocolProtocolHandler的职责是,接收客户端Socket请求,处理Socket请求,将请求包装之后传给容器来处理。

    Coyote架构下,每个ProtocolHandler使用相应的端点类XXXEndpoint(如org.apache.tomcat.util.net.AprEndpoint, org.apache.tomcat.util.net.NioEndpoint,org.apache.tomcat.util.net.JIoEndpoint)监听指定端口并接收Socket请求。在请求传给容器之前,我们需要对请求做一些处理(如是否超时,是否保持连接等),而这些工作都是交给具体协议处理器(ProtocolHandler)的相关类来实现,最后由Adapter包装请求并调用容器的invoke方法。

ProtocolHandler相关类

    为了实现协议处理器的相关功能,Coyote按功能职责抽象了协议处理器的相关类:

l         XXXEndpoint,监听指定端口,接收Socket请求

l         Handler,管理具体协议的Socket请求连接

l         Processor,真正的具体协议处理器

l         Adapter,包装请求,并调用容器的invoke方法(准确的说是Pipeline的第一个Valve

 

ProtocolHandler相关类的配合下,其处理请求的过程如下:

根据支持的协议,通常ProtocolHandler包含一个实现相应协议的Handler实例和XXXEndpoint实例Handler实例接收XXXEndpoint 获得的Socket对象,然后创建相应协议的Processor实例,并将Socket请求交给它来处理,最后Processor调用Adapterservice方法,该方法把请求包装后调用容器的invoke方法。

Adapterservice方法签名如下:

public void service(org.apache.coyote.Request req,

                        org.apache.coyote.Response res)

 

通过以上的分析,我们发现,Adapter是所有请求进入容器的一个阀。在Tomcat的设计中,阀的使用无处不在。

 

示例分析

    下面具体分析一下Tomcat6.x是如何支持HTTP/1.1协议的连接器。创建测试代码如下。

代码片段1,创建支持HTTP/1.1的连接器并启动:

    public static void main(String[] args) {

       try {

           Connector connector = new Connector("HTTP/1.1");

           connector.setPort(8080);

           connector.setContainer(new SimpleContainer());

 

           connector.start();

           Thread.sleep(3000000);

       } catch (Exception e) {

           e.printStackTrace();

       }

    }


代码片段2SimpleContainer的实现,省略了部分没有实现的代码:

public class SimpleContainer implements Container {

 

    private Container container;

 

    public SimpleContainer() {

       this.container = this;

    }

 

    public SimpleContainer(Container container) {

       this.container = container;

    }

 

    @Override

    public Pipeline getPipeline() {

       // TODO Auto-generated method stub

       return new Pipeline() {

 

           @Override

           public Valve getFirst() {

              return new Valve() {

                  @Override

                  public void invoke(Request request, Response response)

                         throws IOException, ServletException {

                     container.invoke(request, response);

                  }

              };

           }

 

 

    @Override

    public void invoke(Request request, Response response) throws IOException, ServletException {

 

       response.setContentType("text/html;charset=gbk");

 

       PrintWriter w = response.getWriter();

       Class c = request.getConnector().

getProtocolHandler().getClass();

       w.print("当前连接器支持的协议为:" +

request.getConnector().getProtocol());

       w.print("<br>");

       w.print("当前连接器的类型为:" + c.getName());

       w.print("<br>");

       w.print("容器方法[invoke]被调用");

    }

}

 


执行该程序,在地址栏输入:http://localhost:8080/index.html,你将看到如下画面:

Tomcat6.0源码学习--Connector架构 - dinstone - Dinstone 的技术博客 

 

Connector接收请求的过程如下:

Tomcat6.0源码学习--Connector架构 - dinstone - Dinstone 的技术博客

Accepter线程接到客户端的Socket请求后,调用JIoEndpointprocessSocket方法分发请求给worker线程来处理。

 

Connector处理请求的过程如下:

Tomcat6.0源码学习--Connector架构 - dinstone - Dinstone 的技术博客

worker线程接收到Socket请求后,将请求委托给Http11ConnectionHandler处理。Http11ConnectionHandler创建Http11Processor并调用其process方法,该方法解析协议内容幷包装请求信息为org.apache.coyote.Requestorg.apache.coyote.Response之后调用CoyoteAdapterservice方法。

 

Connector传递请求的过程如下:

Tomcat6.0源码学习--Connector架构 - dinstone - Dinstone 的技术博客

CoyoteAdapterservice方法内部,将org.apache.coyote.Requestorg.apache.coyote.Response转换为org.apache.catalina.connector.Requestorg.apache.catalina.connector.Response。然后传递请求给容器。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章