对于tomcat的一个链接,最基本的会有3个基本步骤:
1、创建Request,解析请求的参数、数据等
2、创建Response,服务器响应请求用户,封装了服务器的http头、状态、数据、内容等。
3、执行serlvet的service方法,传入request、response,执行业务处理等操作。
介绍tomcat之前先说下HTTP协议。
HTTP:web服务器与客户端进行交互的超文本传输协议,那对于Java tomcat来说直白的说就是tomcat与浏览器的交互,采用的是http的传输协议。http呢,底层也是socket编程。基于TCP/IP协议。
HTTP请求包含三部分:
http请求方法: POST /Example/index.jsp HTTP/1.1
请求头:浏览器的语音(中英文)、会话链接方式、浏览器信息、cookie、访问者服务器信息、访问类型(application/x-www-form)等。
实体:与请求头有个空格,实体就是提交的内容:比如表单内容文本内容、文件流二进制数据等。
HTTP响应也包含三部分:
协议—状态码—描述:HTTP/1.1
200 OK
响应头:返回内容的长度、时间、服务器信息、数据类型(json、text)等。
响应实体段:比如一个html页面,一段二进制数据流,或者一段json数据等。
因此对于我们自己编写简陋的类似tomcat的http服务器时,我们就知道该怎么去解码Request,如何去编码Response。比如我们的请求方式、协议、响应头、及发送的内容都需要按照规则去编码。
举例,比如我编写了一个响应输出流,但没有输出HTTP/1.1,这是用浏览器访问是得不到任何返回内容的。只有加上了HTTP/1.1,浏览器才能显示内容。
String t = "";
//t = "HTTP/1.1 200 OK\r\n\r\n";
t = "HTTP/1.1\r\n";
t += "WWW-Authenticate: [Basic realm=\"Simple
Realm\"] \r\n\r\n";
System.arraycopy(t.getBytes(), 0,dest, 0, t.getBytes().length);
while (ch != -1) {
System.arraycopy(bytes, 0, dest,
t.getBytes().length, bytes.length);
output.write(dest);
//output.write(bytes, 0,ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
注意,我们响应浏览器时response可以输出的是一个文件的二进制数据流,比如jsp、html,也可以是json、字符串等。jsp的动态数据最终会全部形成静态的数据,同时这些类型要给浏览器说明清楚数据类型,解析的时候能正确的解析,比如json、文本等。同时传输过去的内容也需要浏览器能够支持,比如有些js插件一些浏览器就不怎么兼容。
tomcat可以划分为2块:connector(连接器) , container(容器)。container容器有4个:engine,host,context,wrapper。
我们在connector设置container,比如设置context容器,然后context容器添加子容器wrapper,wrapper中设置过滤器。
connector启动了一个线程端口接收链接,针对每个链接会启动一个processor,这个好比mina的processor。但tomcat比较复杂,正式版本tomcat源码启动时,一层层嵌套启动的是Lifecycle(Container、Connector也继承了LifeCycle)的start、startInternal等方法。
我们先化繁为简说简单的tomcat实现思路。这个跟mina很类似,都是服务器链接线程在accept或者是select()等待客户端线程接链接接入。等有链接接入之后就会产生一个专门的线程去与客户端进行业务处理。
不同点是tomcat是采用多线程用Socket、ServerSocket实现了http服务,而且tomcat是同时启动了socket接收服务,及processor业务处理线程。他们采用了notifyall、wait的方式进行线程间的通讯,有链接来了之后,connect通知processor进行处理。而mina是socket接收到客户链接之后,再分配processor线程进行业务处理。
tomcat流程如下:在connector设置container容器,容器由大到小有4类:
engine:整个servlet引擎,比如多个tomcat集群配置
host:包含多个context,一个主机的tomcat有多个web应用
context:对应一个web应用
wrapper:对应一个servlet。
上面的仅且只能包含直接跟在他下面的。比如engine只能包含host,如果包含context就会报错,同时context包含host也会报错。
上面的容器可以包含多个下面的容器,比如context与wrapper是一对多的关系。在wrapper设置servlet,servlet的service方法可以进行业务处理。wrapper与servlet是一对一的关系。
对于tomcat的管道任务,首先搞清楚几个概念:
Valve:阀,有基础阀BasicValve,阀有invoke执行方法。
管道Pipeline addValve invoke -->调用ValveContext的invokeNext()方法。
流程如下:先是调用容器context,然后调用pipeline的invoke,再ValveContext的invokeNext(),调用valve的invoke,调用基础阀,然后再调用子容器wrapper,接着调用子容器的pipeline的invoke,然后在ValveContext的invokeNext(),调用valve的invoke,调用基础阀,然后执行serlvet。基础阀是最先执行的。然后再一步步退回去,执行每个阀中未结束的invoke方法。
我们调试可以看到同一个方法的一个代码块被重复执行多次。因为pipeline有个数组,每个数组的valve都会去执行一次invokeNext方法。
其实我们最简单可能使用的是for循环直接去遍历执行每个valve的方法。但是框架为什么设计这么复杂,我觉得一是代码优雅,最主要是为了便于扩展。就像tomcat的启动bootstrap,调用catalina主类的时候都要用反射。
tomcat的生命周期:
LifeCycle有几个状态:start、before_start、stop等,也有相应的方法。
LifecycleEvent:事件,包含事件、状态(开始、结束等)、数据对象;
LifecycleListener:监听器,lifecycleEvent方法,触发事件调用的方法。某个容器添加监听器,添加到该容器的LifecycleSupport的linsteners数组里面。
LifecycleSupport里面有个linsteners数组,注册的监听器添加到数组里面。LifecycleSupport属于某个容器。
比如说context调用start事件方法。那么context会调用load、子容器、valve阀的start方法。也会触发start事件,调用所有实现了LifecycleListener接口的方法lifecycleEvent。
如果容器没有注册listener,那么就不会有事件被产生。
生命周期就是通过一个顶级容器管理所有组件、子容器的事件,依次进行调用每个容器注册的监听器的lifecycleEvent方法。
生命周期中依次调用的方法很大的作用就是初始化、配置参数,给map、list、stack添加元素。比如类加载器也实现了Lifycycle接口,他的start方法就是初始化实例WebappClassLoader,并且配置repository,workdir。触发监听器时间的时候,利用观察者模式或者for循环调用事件方法。
tomcat类载入器:
为了在加载类中指定某些规则;
为了缓存已经载入的类;
为了实现类的预载入,方便使用。
tomcat有自己的Loader接口,实现类有WebappLoader,该类并没有findClass及loadClass方法,但是他通过使用WebappClassLoader进行类的查找及加载。
tomcat重载是Reloader接口中通过modified()返回值来确定。WebappLoader实现了runnable接口,通过后台线程不断轮询,如果class发生修改,则会通知context重新加载。
tomcat会把加载的class信息映射到ResourceEntry中,包含类的二进制数据,修改时间长整数,类,url等等,放在一个hashmap缓存中。
repository添加仓库,tomcat查找资源的目录。
path是url路径,docbase是本地项目路径。
通过httpclient可以模拟劫持sessionid
session 和 request,我有段时间一直认为request的属性一定包含在session之中,其实不是这样的。他们是作用域不同,没有包含关系,一个是在会话中,一个是在一次http访问中。
session与context容器有关联,context通过设置session的管理器Manger 管理容器的会话,sessions通过一个map缓存该容器的所有session。
response、request是创建connect连接的时候建立的,即用户发送http请求的时候进行创建。session也是用户连接的时候创建。但是session是由context全局变量manger进行管理。而request及response是在链接方法内部创建,局部变量。
session与cookie都是服务器端产生的,session是在服务器内存中,使用在服务器上。cookie保存在客户端本地,给浏览器用。cookie由response传送给浏览器。
领域: realm,验证用户身份, 与context一一对应。
主体对象:Principal,与领域对象关联,必须有个用户名密码对,角色可选。
登录配置:LoginConfig,包含1个领域对象,封装领域对象的验证方法。web.xml中如果有login-config元素则会创建LoginConfig。
验证器:Authenticator,AuthenticatorBase继承了valve。
SecurityCollection securityCollection = newSecurityCollection();
securityCollection.addPattern("/");
securityCollection.addMethod("GET");
SecurityConstraintconstraint = new SecurityConstraint();
constraint.addCollection(securityCollection);
constraint.addAuthRole("manager");
LoginConfigloginConfig = new LoginConfig();
loginConfig.setRealmName("Simple Realm");
// add realm
Realmrealm = new SimpleRealm();
context.setRealm(realm);
context.addConstraint(constraint);
context.setLoginConfig(loginConfig);
SecurityCollection设置验证路径、验证方法
SecurityConstraint加入验证规则, 同时设置角色
context构造器的作用呢,就是为standcontext实例提供基础阀。如果子容器想执行定时任务,则重载backgroundProcess()方法。
当我们设置了context的父容器host时,那我们的访问路径也要添加context设置的path,因为根据这个path在host的map中去匹配context。为什么要有host容器,因为ApplicationContext的getResource中有段代码用到了host.
服务器组件:server
服务组件:service
服务器组件与服务组件是1对多的关系。service再设置添加engine,engine再设置添加host…..。
那为什么需要这样配置呢,1、可以统一管理,connector与容器可以在一个代码里面start启动,stop停止。好像就这个用处。
关闭钩子:Runtime.getRuntime().addShutdownHook。
tomcat的部署器:通过HostConfig进行部署。流程如下:
StandardHost.start--->HostConfig.start()--->deployApps()
ManagerServlet,特殊的servlet,继承了ContainerServlet,在StandardWrapper的loadServlet方法中setWrapper,获得wrapper容器,通过getParent同样获得context,host容器,这样就可以进行很多web应用的操作,list查看所有应用,deploy部署应用,start启动应用等等。 这个跟spring的继承了aware接口的容器类一样,也是通过set容器到这类类中,然后本身就可以操纵容器。
普通servlet可以获取ApplicationContext,这个不是容器,只是servlet的运行环境。跟standardContext不一样。ApplicationContext里面有standardContext,进行数据的配置。
JMS:jvm提供了一套bean的管理机制,通过mbean管理对外暴露一些方法,供其他应用进行调用。
MBean服务器: MBeanServer
对象名称: ObjectName
MBean,对应要管理的bean。
�W8�7�T