我们知道了tomcat的整体框架了, 也明白了里面都有些什么组件, 以及各个组件是干什么用的了。
http://www.csdn.net/develop/read_article.asp?id=27225
我想,接下来我们应该去了解一下 tomcat 是如何处理jsp和servlet请求的。
1. 我们以一个具体的例子,来跟踪tomcat,看看它是如何把request一层一层地递交给下一个容器,并最后交给wrapper来处理的。
以http://localhost:8080/web/login.jsp为例子
(以下例子,都是以tomcat4 源码为参考)
这篇心得主要分为3个部分: 前期, 中期, 和末期。
前期:讲解了在浏览器里面输入一个url,是怎么被tomcat抓住的。
中期:讲解了被tomcat抓住后,又是怎么在各个容器里面穿梭, 最后到达最后的处理地点。
末期:讲解到达最后的处理地点后,又是怎么具体处理的。
2、 前期 request的born.
在这里我先简单讲一下request这个东西。
我们先看着这个url:http://localhost:8080/web/login.jsp 它是动用了8080端口来进行socket通讯的。
我们知道, 通过
inputstream in = socket.getinputstream() 和
outputstream out = socket.getoutputstream()
就可以实现消息的来来往往了。
但是如果把stream给应用层看,显然操作起来不方便。
所以,在tomcat 的connector里面, socket被封装成了request和response这两个对象。
我们可以简单地把request看成管发到服务器来的数据,把response看成想发出服务器的数据。
但是这样又有其他问题了啊? request这个对象是把socket封装起来了, 但是他提供的又东西太多了。
诸如request.getauthorization(), request.getsocket()。 像authorization这种东西开发人员拿来基本上用不太着,而像socket这种东西,暴露给开发人员又有潜在的危险。 而且啊, 在servlet specification里面标准的通信类是servletrequest和httpservletrequest,而非这个request类。 so, so, so. tomcat必须得捣持捣持request才行。 最后tomcat选择了使用捣持模式(应该叫适配器模式)来解决这个问题。它把org.apache.catalina.request 捣持成了 org.apache.coyote.tomcat4.coyoterequest。 而coyoterequest又实现了servletrequest和httpservletrequest 这两种接口。 这样就提供给开发人员需要且刚刚需要的方法了。
ok, 让我们在 tomcat的顶层容器 - standardengin 的invoke()方法这里设置一个断点, 然后访问
http://localhost:8080/web/login.jsp, 我们来看看在前期都会路过哪些地方:
1. run(): 536, java.lang.thread, thread.java
currentthread
2. run():666, org.apache.tomcat.util.threads.threadpool$controlrunnable, threadpool.java
threadpool
3. runit():589, org.apache.tomcat.util.net.tcpworkerthread, pooltcpendpoint.java
threadworker
4. processconnection(): 549
org.apache.coyote.http11.http11protocol$http11connectionhandler, http11protocol.java
http protocol parser
5. process(): 781, org.apache.coyote.http11.http11processor, http11processor.java
http request processor
6. service(): 193, org.apache.coyote.tomcat4.coyoteadapter,coyoteadapter.java
adapter
7. invoke(): 995, org.apache.catalina.core.containerbase, containerbase.java
standardengin
1. 主线程
2. 启动线程池.
3. 调出线程池里面空闲的工作线程。
4. 把8080端口传过来由httpd协议封装的数据,解析成request和response对象。
5. 使用http11processor来处理request
6. 在http11processor里面, 又会call coyoteadapter来进行适配处理,把request适配成实现了servletrequest和httpservletrequest接口的coyoterequest.
7. 到了这里,前期的去毛拔皮工作就基本上搞定,可以交给standardengin 做核心的处理工作了。
3. 中期。 在各个容器间的穿梭。
request在各个容器里面的穿梭大致是这样一种方式:
每个容器里面都有一个管道(pipline), 专门用来传送request用的。
管道里面又有好几个阀门(valve), 专门用来过滤request用的。
在管道的低部通常都会放上一个默认的阀们。 这个阀们至少会做一件事情,就是把request交给子容器。
让我们来想象一下:
当一个request进入一个容器后, 它就在管道里面流动,波罗~ 波罗~ 波罗~ 地穿过各个阀门。在流到最后一个阀门的时候,吧唧~ 那个该死的阀门就把它扔给了子容器。 然后又开始 波罗~ 波罗~ 波罗~ ... 吧唧~.... 波罗~ 波罗~ 波罗~ ....吧唧~....
就是通过这种方式, request 走完了所有的容器。( 感觉有点像消化系统,最后一个地方有点像那里~ )
ok, 让我们具体看看都有些什么容器, 各个容器里面又都有些什么阀门,这些阀们都对我们的request做了些什么吧:
3.1 standardengin 的pipeline里面放的是:standardenginvalve
在这里,valve做了三件事:
1. 验证传递过来的request是不是httpservletrequest.
2 验证传递过来的 request 是否携带了host header信息.
3 选择相应的host去处理它。(一般我们都只有一个host:localhost,也就是127.0.0.1)。
到了这个地方,我们的request就已经完成了在engin这个部分的历史使命,通向前途未卜的下一站: host了。
3.2 standardhost 的pipline里面放的是: standardhostvalve
1. 验证传递过来的request是不是httpservletrequest.
2. 根据request来确定哪个context来处理。
context其实就是webapp,比如http://localhost:8080/web/login.jsp
这里web就是context罗!
3. 既然确定了是哪个context了,那么就应该把那个context的classloader付给当前线程了。
thread.currentthread().setcontextclassloader(context.getloader().getclassloader());
这样request就只看得见指定的context下面的classes啊, jar啊这些,而看不见tomcat本身的类,什么engin啊, valve啊。不然还得了啊!
4. 既然request到了这里了,看来用户是准备访问web这个web app了,咋们得更新一下这个用户的session不是! ok , 就由manager更新一下用户的session信息
5. 交给具体的context 容器去继续处理request.
6. context处理完毕了,把classloader还回来。
3.3 standardcontext 的pipline里面放的是: standardcontextvalve
1. 验证传递过来的request是不是httpservletrequest.
2. 如果request意图不轨,想要访问/meta-inf, /web-inf这些目录下的东西,呵呵,没有用d!
3. 这个时候就会根据request到底是servlet,还是jsp,还是静态资源来决定到底用哪种wrapper来处理这个reqeust了。
4. 一旦决定了到底用哪种wrapper,ok,交给那个wrapper处理。
4. 末期。 不同的需求是怎么处理的.
standardwrapper
之前对wrapper没有做过讲解,其实它是这样一种东西。
我们在处理request的时候,可以分成3种。
处理静态的: org.apache.catalina.servlets.defaultservlet
处理jsp的:org.apache.jasper.servlet.jspservlet
处理servlet的:org.apache.catalina.servlets.invokerservlet
不同的request就用这3种不同的servlet去处理。
wrapper就是对它们的一种简单的封装,有了wrapper后,我们就可以轻松地拦截每次的request。也可以容易地调用servlet的init()和destroy()方法, 便于管理嘛!
具体情况是这么滴:
如果request是找jsp文件,standardwrapper里面就会封装一个org.apache.jasper.servlet.jspservlet去处理它。
如果request是找 静态资源 ,standardwrapper里面就会封装一个org.apache.jasper.servlet.defaultservlet 去处理它。
如果request是找servlet ,standardwrapper里面就会封装一个org.apache.jasper.servlet.invokerservlet 去处理它。
standardwrapper同样也是容器,既然是容器, 那么里面一定留了一个管道给request去穿,管道低部肯定也有一个阀门(注1),用来做最后一道拦截工作.
在这最底部的阀门里,其实就主要做了两件事:
一是启动过滤器,让request在n个过滤器里面筛一通,如果ok! 那就pass。 否则就跳到其他地方去了。
二是servlet.service((httpservletrequest) request,(httpservletresponse) response); 这个方法.
如果是 jspservlet, 那么先把jsp文件编译成servlet_xxx, 再invoke servlet_xxx的servie()方法。
如果是 defaultservlet, 就直接找到静态资源,取出内容, 发送出去。
如果是 invokerservlet, 就调用那个具体的servlet的service()方法。
ok! 完毕。
注1: standardwrapper 里面的阀门是最后一道关口了。 如果这个阀门欲意把request交给standardwrapper 的子容器处理。 对不起, 在设计考虑的时候, wrapper就被考虑成最末的一个容器, 压根儿就不会给wrapper添加子容器的机会! 如果硬是要调用addchild(), 立马抛出illegalargumentexception!
参考:
<>
<>
作者blog:
相关文章
]]>