Tomcat架构入门-请求转换

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

一、Tomcat的组成

关于Tomcat的架构其实可以通过Tomcat下的server.xml做一个基本了解,在面试过程中有时候也会问到server.xml一些内容,包括Tomcat的优化其实很多都是在这里完成的,所以需要引起重视。

<!-- Prevent memory leaks due to use of particular java/javax APIs-->

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"/>

connectionTimeout="20000"

redirectPort="8443"/>

resourceName="UserDatabase"/>

unpackWARs="true"autoDeploy="true">

prefix="localhost_access_log"suffix=".txt"

pattern="%h %l %u %t &quot;%r&quot; %s %b"/>

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

tomcat整体架构.jpg

二、核心组件

结合这server.xml和上面这张图,来简单的了解一下各个组件。

Server是Tomcat的顶层组件。在server.xml中Server元素表示整个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的所有请求,并将完成的响应返回给连接器,最终传输回客户端。

Host是Engine的子容器,一个Engine内可以有一个或多个Host,它表示的一个虚拟主机,

Context表示在特定虚拟主机中运行的Web应用程序,每一个Web应用程序都基于WAR文件或包含相应解压缩内容的相应目录。每一个虚拟主机中可以包含多个Context。

在Service中还有一个很重要的组件就是Executor,Executor表示是Tomcat中的组件之间共享的线程池。 Tomcat已经默认为每个连接器都创建了一个线程池。自定义线程池可以供Connector共享,也可以和其他组件共享该线程池。

除了上面的核心组件之外,还有其他一些嵌套组件,比如server.xml中定义的Listener、Realm、GlobalNamingResources等。这里就不细述了。

下图是一个用户请求和响应的流程示意图,实际情况当然要复杂许多。

请求流程.jpg

三、请求转换

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

Connector.jpg

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

Http11NioProtocol.jpg

ProtocolHandler主要是处理网络连接,将字节流封装成 Request对象,再将Request 适配成 Servlet 处理ServletRequest 对象这几个动作,用组件封装起来了,ProtocolHandler包括了三个组件:Endpoint、Processor、Adapter。

而Endpoint的创建则在其默认构造函数实现,以Http11NioProtocol为例,其代码如下:

publicHttp11NioProtocol(){

super(newNioEndpoint());

}

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

代码如下:

protectedclassSocketProcessorextendsSocketProcessorBase{

publicSocketProcessor(SocketWrapperBase<NioChannel> socketWrapper, SocketEvent event){

super(socketWrapper, event);

}

@Override

protectedvoiddoRun(){

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{

inthandshake = -1;

try{

if(key !=null) {

if(socket.isHandshakeComplete()) {

handshake =0;

}elseif(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);

}

}elseif(handshake == -1) {

poller.cancelledKey(key, socketWrapper);

}elseif(handshake == SelectionKey.OP_READ){

socketWrapper.registerReadInterest();

}elseif(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对象,因为代码较多,我只粘贴一部分:

publicSocketStateprocess(SocketWrapperBase<S> wrapper, SocketEvent status){

if(wrapper ==null) {

returnSocketState.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());

}elseif(negotiatedProtocol.equals("http/1.1")) {

}else{

if(getLog().isDebugEnabled()) {

getLog().debug(sm.getString("abstractConnectionHandler.negotiatedProcessor.fail",negotiatedProtocol));

}

returnSocketState.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

protectedProcessorcreateProcessor(){

Http11Processor processor =newHttp11Processor(this, adapter);

returnprocessor;

}

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

@Override

protectedvoidinitInternal()throwsLifecycleException{

super.initInternal();

.......

// Initialize adapter

adapter =newCoyoteAdapter(this);

protocolHandler.setAdapter(adapter);

if(service !=null) {

protocolHandler.setUtilityExecutor(service.getServer().getUtilityExecutor());

}

......

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

publicHttp11Processor(AbstractHttp11Protocol<?> protocol, Adapter adapter){

super(adapter);

this.protocol = protocol;

httpParser =newHttpParser(protocol.getRelaxedPathChars(),

protocol.getRelaxedQueryChars());

inputBuffer =newHttp11InputBuffer(request, protocol.getMaxHttpHeaderSize(),

protocol.getRejectIllegalHeaderName(), httpParser);

request.setInputBuffer(inputBuffer);

outputBuffer =newHttp11OutputBuffer(response, protocol.getMaxHttpHeaderSize());

response.setOutputBuffer(outputBuffer);

// 省略代码

.....

}

而调用父类构造函数的时候会创建两个对象,Request和Response,对应的具体类是org.apache.coyote.Request和org.apache.coyote.Response。这两个对象下面还会使用到,不要混淆了。

回归到正题,Processor创建完成后会调用它的process方法(AbstractProcessorLight.process)其方法内部会调用具体的service方法,这里调用的是Http11Processor.service方法,这个方法内内容很多,我也并没有仔细的看,应该是对org.apache.coyote.Request和org.apache.coyote.Response进行了参数设置,核心的地方在于调用adapter的service方法,代码如下:

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);

}

}

这里会调用CoyoteAdapter的service方法,代码如下:

@Override

publicvoidservice(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);

}

booleanasync=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 =newAtomicBoolean(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();

longtime = System.currentTimeMillis() - req.getStartTime();

if(context !=null) {

context.logAccess(request, response, time,false);

}elseif(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.Request和org.apache.coyote.Response两个变量,方法内会根据入参创建出org.apache.catalina.connector.Request和org.apache.catalina.connector.Response,而这两个对象分别继承了HttpServletRequest和HttpServletResponse,也就是说实际上用户请求在这里完成了转换,变成了我们非常熟悉的HttpServletRequest和HttpServletResponse。

然后执行下面这段代码:

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

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

整个Connector执行流程如下图:

请求转换流程图.png

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

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

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

推荐阅读更多精彩内容