Tomcat架构入门-请求转换

作为Java开发人员来说Tomcat应该在熟悉不过了,自己最近空闲时间比较多,前天突然很好奇到底用户的请求是怎么被转化成HttpServletRequest,所以自己想着看看源码,但是因为考虑到一直以来自己对 Tomcat的架构并不熟悉,所以我决定在看源码前好好的熟悉下Tomcat的架构,看看Tomcat都有哪些组件以及它们的作用分别是什么,这个过程我主要是参考官方文档,另外就是Tomcat的源码。

一、Tomcat的组成

关于Tomcat的架构其实可以通过Tomcat下的server.xml做一个基本了解。

<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <!-- Prevent memory leaks due to use of particular java/javax APIs-->
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <GlobalNamingResources>

    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="Catalina">

    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

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

    <Engine name="Catalina" defaultHost="localhost">

      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

      </Host>
    </Engine>
  </Service>
</Server>

然后通过网上的一张架构图来了解各个层级之间的关系


tomcat整体架构.jpg

二、核心组件

结合这server.xml和上面这张图,来简单的了解一下各个组件。
Server是Tomcat的顶层组件。在server.xmlServer元素表示整个Catalina Servlet容器。因此,它必须是server.xml配置文件中的单个最外层元素,简单一点可以将Server看做是Tomcat服务器本身,Server管理着整个Tomcat生命周期。一个Server内可以包了多个listener,可以包含一个或多个Service
Service是Tomcat的另一个顶层组件,Service元素由一个或多个Connector组件的组合,这些Connector共享一个Engine组件以处理传入的请求。Service的功能就是对外提供服务。
接下来是Tomcat的几个比较重要的组件:
Connector顾名思义就是连接器,它连接的是用户请求和容器,即用户的请求到达Connector后,由它将用户请求进行包装,然后传递给具体的容器Engine进行处理,之后再通过Connector将结果进行包装转换传递给用户。Connector功能就是处理用户的请求和响应。Service中的Engine对外是不可见的,所有与Engine的交互必须先经过Connector处理。目前Tomcat有三种Connector,它们主要区别在于支持的协议不同。最常见的就是支持http/1.1,另外还有支持http/2以及ajp(Apache Jserv Protocol,一种二进制协议)。关于这个三种连接器上相关的属性建议看下官方文档,因为内容还是比较多的。在对Tomcat进行优化事很多时候都会需要修改Connector上的相关参数,比如最大连接数、最大线程数量等等。所以这部分内容是非常重要的,也是核心。
Engine代表的处理请求的入口。它接受并处理来自Connector的所有请求,并将完成的响应返回给连接器,最终传输回客户端。
HostEngine的子容器,一个Engine内可以有一个或多个Host,它表示的一个虚拟主机,
Context表示在特定虚拟主机中运行的Web应用程序,每一个Web应用程序都基于WAR文件或包含相应解压缩内容的相应目录。每一个虚拟主机中可以包含多个Context
Service中还有一个很重要的组件就是ExecutorExecutor表示是Tomcat中的组件之间共享的线程池。 Tomcat已经默认为每个连接器都创建了一个线程池。自定义线程池可以供Connector共享,也可以和其他组件共享该线程池。
除了上面的核心组件之外,还有其他一些嵌套组件,比如server.xml中定义的ListenerRealmGlobalNamingResources等。这里就不细述了。
下图是一个用户请求和响应的流程示意图,实际情况当然要复杂许多。

请求流程.jpg

三、请求转换

下面主要看看Connector是如何将用户请求一步一步进行转换的,我们先看下对应类的继承体系

Connector.jpg

Connector封装了两个主要的成员变量,一个是ProtocolHandler,一个是Adapter,前者根据不同的协议有不同的实现类型,我们以Http11NioProtocol为例,其类的继承体系如下
Http11NioProtocol.jpg

ProtocolHandler主要是处理网络连接,将字节流封装成 Request对象,再将Request 适配成 Servlet 处理ServletRequest 对象这几个动作,用组件封装起来了,ProtocolHandler包括了三个组件:EndpointProcessorAdapter
Endpoint的创建则在其默认构造函数实现,以Http11NioProtocol为例,其代码如下:

   public Http11NioProtocol() {
        super(new NioEndpoint());
    }

Endpoint主要用来处理底层的Socket网络连接,在Endpoint具体实现类中里面有个SocketProcessor的内部类,它负责将Endpoint接收到的Socket请求转化成org.apache.coyote.Request请求。
代码如下:

    protected class SocketProcessor extends SocketProcessorBase<NioChannel> {

        public SocketProcessor(SocketWrapperBase<NioChannel> socketWrapper, SocketEvent event) {
            super(socketWrapper, event);
        }

        @Override
        protected void doRun() {
            NioChannel socket = socketWrapper.getSocket();
            SelectionKey key = socket.getIOChannel().keyFor(socket.getSocketWrapper().getPoller().getSelector());
            Poller poller = NioEndpoint.this.poller;
            if (poller == null) {
                socketWrapper.close();
                return;
            }
            try {
                int handshake = -1;
                try {
                    if (key != null) {
                        if (socket.isHandshakeComplete()) {

                            handshake = 0;
                        } else if (event == SocketEvent.STOP || event == SocketEvent.DISCONNECT ||
                                event == SocketEvent.ERROR) {
                            handshake = -1;
                        } else {
                            handshake = socket.handshake(key.isReadable(), key.isWritable());
                            event = SocketEvent.OPEN_READ;
                        }
                    }
                } catch (IOException x) {
                    handshake = -1;
                    if (log.isDebugEnabled()) log.debug("Error during SSL handshake",x);
                } catch (CancelledKeyException ckx) {
                    handshake = -1;
                }
                if (handshake == 0) {
                    SocketState state = SocketState.OPEN;
                    // Process the request from this socket
                    if (event == null) {
                        state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
                    } else {
                        state = getHandler().process(socketWrapper, event);
                    }
                    if (state == SocketState.CLOSED) {
                        poller.cancelledKey(key, socketWrapper);
                    }
                } else if (handshake == -1 ) {
                    poller.cancelledKey(key, socketWrapper);
                } else if (handshake == SelectionKey.OP_READ){
                    socketWrapper.registerReadInterest();
                } else if (handshake == SelectionKey.OP_WRITE){
                    socketWrapper.registerWriteInterest();
                }
            } catch (CancelledKeyException cx) {
                poller.cancelledKey(key, socketWrapper);
            } catch (VirtualMachineError vme) {
                ExceptionUtils.handleThrowable(vme);
            } catch (Throwable t) {
                log.error(sm.getString("endpoint.processing.fail"), t);
                poller.cancelledKey(key, socketWrapper);
            } finally {
                socketWrapper = null;
                event = null;
                //return to cache
                if (running && !paused && processorCache != null) {
                    processorCache.push(this);
                }
            }
        }
    }

代码中getHandler().process()方法会创建一个Processor对象,因为代码较多,我只粘贴一部分:

public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
          if (wrapper == null) {
              return SocketState.CLOSED;
          }
          S socket = wrapper.getSocket();
          Processor processor = connections.get(socket);
            ....
          if (processor == null) {
                  String negotiatedProtocol = wrapper.getNegotiatedProtocol();
                  if (negotiatedProtocol != null && negotiatedProtocol.length() > 0) {
                      UpgradeProtocol upgradeProtocol = getProtocol().getNegotiatedProtocol(negotiatedProtocol);
                      if (upgradeProtocol != null) {
                          processor = upgradeProtocol.getProcessor(wrapper, getProtocol().getAdapter());
                      } else if (negotiatedProtocol.equals("http/1.1")) {
                      } else {
                          if (getLog().isDebugEnabled()) {
                              getLog().debug(sm.getString("abstractConnectionHandler.negotiatedProcessor.fail",negotiatedProtocol));
                          }
                          return SocketState.CLOSED;
                      }
                  }
              }
              if (processor == null) {
                  processor = recycledProcessors.pop();
                  if (getLog().isDebugEnabled()) {
                        getLog().debug(sm.getString("abstractConnectionHandler.processorPop",processor));
                  }
              }
              if (processor == null) {
                  processor = getProtocol().createProcessor();
                  register(processor);
              }
      // 省略代码
      ......
}

上面的代码在AbstractProtocol的内部类ConnectionHandler中,其中socket变量其实是一个NioChannel实例。这个方法代码中会根据不同情形创建相关的Processor。比如我这里会获取相应的AbstractHttp11Protocol然后调用其createProcessor()方法创建Http11Processor对象。

    @Override
    protected Processor createProcessor() {
        Http11Processor processor = new Http11Processor(this, adapter);
        return processor;
    }

adapter其实在Connector初始化的时候就创建完成了,并将其添加到protocolHandler中,下面是ConnectorinitInternal()方法:

    @Override
    protected void initInternal() throws LifecycleException {
        super.initInternal();
        .......
        // Initialize adapter
        adapter = new CoyoteAdapter(this);
        protocolHandler.setAdapter(adapter);
        if (service != null) {
            protocolHandler.setUtilityExecutor(service.getServer().getUtilityExecutor());
        }
      ......

另外需要关注的一点是创建Http11Processor的时候,会调用其父类的构造函数:

    public Http11Processor(AbstractHttp11Protocol<?> protocol, Adapter adapter) {
        super(adapter);
        this.protocol = protocol;

        httpParser = new HttpParser(protocol.getRelaxedPathChars(),
                protocol.getRelaxedQueryChars());

        inputBuffer = new Http11InputBuffer(request, protocol.getMaxHttpHeaderSize(),
                protocol.getRejectIllegalHeaderName(), httpParser);
        request.setInputBuffer(inputBuffer);

        outputBuffer = new Http11OutputBuffer(response, protocol.getMaxHttpHeaderSize());
        response.setOutputBuffer(outputBuffer);
        // 省略代码
        .....
    }

而调用父类构造函数的时候会创建两个对象,RequestResponse,对应的具体类是org.apache.coyote.Requestorg.apache.coyote.Response。这两个对象下面还会使用到,不要混淆了。
回归到正题,Processor创建完成后会调用它的process方法(AbstractProcessorLight.process)其方法内部会调用具体的service方法,这里调用的是Http11Processor.service方法,这个方法内内容很多,我也并没有仔细的看,应该是对org.apache.coyote.Requestorg.apache.coyote.Response进行了参数设置,核心的地方在于调用adapterservice方法,代码如下:

            if (getErrorState().isIoAllowed()) {
                try {
                    rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
                    // 适配器转换,request和response
                    getAdapter().service(request, response);
                    if(keepAlive && !getErrorState().isError() && !isAsync() &&
                            statusDropsConnection(response.getStatus())) {
                        setErrorState(ErrorState.CLOSE_CLEAN, null);
                    }
                } catch (InterruptedIOException e) {
                    setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
                } catch (HeadersTooLargeException e) {
                    log.error(sm.getString("http11processor.request.process"), e);
                    if (response.isCommitted()) {
                        setErrorState(ErrorState.CLOSE_NOW, e);
                    } else {
                        response.reset();
                        response.setStatus(500);
                        setErrorState(ErrorState.CLOSE_CLEAN, e);
                        response.setHeader("Connection", "close"); // TODO: Remove
                    }
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    log.error(sm.getString("http11processor.request.process"), t);
                    response.setStatus(500);
                    setErrorState(ErrorState.CLOSE_CLEAN, t);
                    getAdapter().log(request, response, 0);
                }
            }

这里会调用CoyoteAdapterservice方法,代码如下:

    @Override
    public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception {
        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);
        if (request == null) {
            request = connector.createRequest();
            request.setCoyoteRequest(req);
            response = connector.createResponse();
            response.setCoyoteResponse(res);

            request.setResponse(response);
            response.setRequest(request);

            req.setNote(ADAPTER_NOTES, request);
            res.setNote(ADAPTER_NOTES, response);
            req.getParameters().setQueryStringCharset(connector.getURICharset());
        }

        if (connector.getXpoweredBy()) {
            response.addHeader("X-Powered-By", POWERED_BY);
        }
        boolean async = false;
        boolean postParseSuccess = false;
        req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());
        try {
            postParseSuccess = postParseRequest(req, request, res, response);
            if (postParseSuccess) {
                request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
                connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);
            }
            if (request.isAsync()) {
                async = true;
                ReadListener readListener = req.getReadListener();
                if (readListener != null && request.isFinished()) {
                    ClassLoader oldCL = null;
                    try {
                        oldCL = request.getContext().bind(false, null);
                        if (req.sendAllDataReadEvent()) {
                            req.getReadListener().onAllDataRead();
                        }
                    } finally {
                        request.getContext().unbind(false, oldCL);
                    }
                }
                Throwable throwable =(Throwable)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

                if (!request.isAsyncCompleting() && throwable != null) {
                    request.getAsyncContextInternal().setErrorState(throwable, true);
                }
            } else {
                request.finishRequest();
                response.finishResponse();
            }

        } catch (IOException e) {
            // Ignore
        } finally {
            AtomicBoolean error = new AtomicBoolean(false);
            res.action(ActionCode.IS_ERROR, error);
            if (request.isAsyncCompleting() && error.get()) {
                res.action(ActionCode.ASYNC_POST_PROCESS,  null);
                async = false;
            }

            // Access log
            if (!async && postParseSuccess) {
                Context context = request.getContext();
                Host host = request.getHost();
                long time = System.currentTimeMillis() - req.getStartTime();
                if (context != null) {
                    context.logAccess(request, response, time, false);
                } else if (response.isError()) {
                    if (host != null) {
                        host.logAccess(request, response, time, false);
                    } else {
                        connector.getService().getContainer().logAccess(request, response, time, false);
                    }
                }
            }

            req.getRequestProcessor().setWorkerThreadName(null);
            if (!async) {
                updateWrapperErrorCount(request, response);
                request.recycle();
                response.recycle();
            }
        }
    }

这个方法的入参为org.apache.coyote.Requestorg.apache.coyote.Response两个变量,方法内会根据入参创建出org.apache.catalina.connector.Requestorg.apache.catalina.connector.Response,而这两个对象分别继承了HttpServletRequestHttpServletResponse,也就是说实际上用户请求在这里完成了转换,变成了我们非常熟悉的HttpServletRequestHttpServletResponse
然后执行下面这段代码:

connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

即通过Connector一步步将requestresponse丢给容器进行处理,而且从方法名称也可以看的出来,实际上是由Pipeline进行处理的,这个就暂不深究了。也就是说到这里我们的HttpServletRequestHttpServletResponse会被容器进行处理了,在Connector的流程执行完成,之后就是有容器进行处理过程了,这里就不再继续往下看了。
整个Connector执行流程如下图:

请求转换流程图.png

通过以上代码和流程图基本上就搞清楚了,用户请求是如何变成我们熟悉的HttpServletRequestHttpServletResponse的,当然实际过程要复杂很多,我只是简单的通过跟踪代码了解了大概的过程,具体代码内容并没有详细的去看,后期如果有需要的话自己会挑选一部分进行阅读,比如今天的Connector这部分。


本次的学习收获主要有两点一是Tomcat的架构,即其组成及各组件的作用,当然这部分没有去深入学习,二是请求转换,即用户请求过来之后是如何转换成HttpServletRequestHttpServletResponse的,这部分主要理清了大概的执行流程,并简单的跟踪了代码,如果后期有需要会在来具体的分析这部分代码。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352