1 tomcat是模块化的服务器,由两大模块:connector和container。
connector主要用来处理网络通讯,接收tcp请求,构造httprequest和httpresponse对象,然后传递给container。
容器的主要任务是触发Servlet.service(ServletRequest arg0, ServletResponse arg1),响应用户请求。
connector和container是解耦的,通过接口规范协作。
org.apache.catalina.Container是一个接口,按照范围从大到小,他的实现类有四个,这几个实现类的关系类似于组合模式的树结构。范围最大的是Engine,他表示整个catalina servlet引擎,他包含一个或者多个Host、Context ;Host表示一个虚拟主机,包含一个或多个Context(好比在一个tomacat服务器下部署多个应用);Context表示一个独立的ServletContext(也就是一个应用,对应的部署描述符是web.xml),他包含一个或多个Wrapper(当需要多个不同的servlet的时候,就会引用多个wrapper子容器)。wraper表示web应用中部署描述符定义的一个servlet,他控制这个servlet的生命周期,负责创建和销毁servlet。wraper不能再有子容器。要部署一个web应用,不一定需要所有的容器。
一般来说,一个connector会引用一个容器,这个容器可能是层级结构的。当接受到客户端请求的时候,connector会调用容器的invoke,容器引用一个pipeline,把请求传递给pipeline,、在pipeline里面顺序执行valve链。如果容器是层级结构的,那么父容器的某个valve会根据某种映射选择一个子容器,子容器执行和父容器类似的过程,请求会横向或者纵向的传递下去。例如,当context接受到一个请求时,他会调用自身的pipeline,执行StandardContextValve,这个value会根据规则找到合适的子容器wrapper,再由wrapper处理请求。
何时需要engine呢?当需要监控到整个engine的请求或者使用独立的connector却需要支持多个host的时候;
用host的时机类似。当和web server 如apache结合使用的时候,一般不需要engine或者host,因为连接器会利用web server的功能确定合适的context或者wrapper.
2 http 服务器实现的主体是Connector.start()(基于server socket),connector.CoyoteAdapter.service(Request req, Response res)内部实现http的处理逻辑。
3 官方网站的学习资料十分重要。不可不看
4
核心类图
从类图结构可以看出,tomcat具有良好的模块化结构,注重针对接口编程,类的设计具有充分的可扩展性。抽象和实现分离,在通信层面支持bio,nio,apr。
5 处理流程
7.0.27版本的http请求处理流程
Connector.initInternal()-》Connector.startInternal()
-》AbstractProtocol.start()-》AbstractEndpoint.start()此处采用了模板方法,AbstractEndpoint.startInternal()具体实现由子类定义 -》以http11为例子进行推演JIoEndpoint.startInternal()-》AbstractEndpoint.startAcceptorThreads启动接收器-》JIoEndpoint.Acceptor.run()-》当有连接请求进来时处理请求,JIoEndpoint.processSocket(Socket socket),以线程池的方式处理getExecutor().execute(new SocketProcessor(wrapper))-》JIoEndpoint.Handler.process(SocketWrapper<Socket> socket, SocketStatus status),此处handler的具体实现类对应Http11ConnectionHandler extends AbstractConnectionHandler-》
AbstractProtocol.AbstractConnectionHandler.process(SocketWrapper<S> socket, SocketStatus status)对请求进行处理-》AbstractHttp11Processor.process(SocketWrapper<S> socketWrapper)构造http请求和http响应,触发servlet的service-》CoyoteAdapter.service(Request req, Response res),CoyoteAdapter类作为请求处理器-》coyoteAdapter关联着一个org.apache.catalina.connector.Connector,由connector调用 connector.getService().getContainer().getPipeline().getFirst().invoke(request, response),委托容器处理请求(webx的pipeline借鉴此处)-》pipeline执行至StandardWrapperValve.invoke(Request request, Response response)-》ApplicationFilterChain.doFilter(ServletRequest request, ServletResponse response),在这个filter里面进入我们熟悉的Servlet.service(ServletRequest arg0, ServletResponse arg1)过程。
(小记,在分析代码过程中state = handler.process(socket,status);,直接在eclipse下面用ctrl+鼠标,没有直接显示出handler的实现类,而实际上是有实现类的,需要先点击到接口里面,在按F4)
6 tomcat的通讯层的实现主要在Endpoint,例如JIoEndpoint,在JIoEndpoint.Acceptor接受连接,拿到套接字后转交给线程池,由线程池执行SocketProcessor。
acceptor接受一个请求的处理逻辑
int errorDelay = 0; // Loop until we receive a shutdown command while (running) { // Loop if endpoint is paused while (paused && running) { state = AcceptorState.PAUSED; try { Thread.sleep(50); } catch (InterruptedException e) { // Ignore } } if (!running) { break; } state = AcceptorState.RUNNING; try { //if we have reached max connections, wait countUpOrAwaitConnection(); Socket socket = null; try { // Accept the next incoming connection from the server // socket socket = serverSocketFactory.acceptSocket(serverSocket); } catch (IOException ioe) { // Introduce delay if necessary errorDelay = handleExceptionWithDelay(errorDelay); // re-throw throw ioe; } // Successful accept, reset the error delay errorDelay = 0; // Configure the socket if (running && !paused && setSocketOptions(socket)) { // Hand this socket off to an appropriate processor if (!processSocket(socket)) { // Close socket right away closeSocket(socket); } } else { // Close socket right away closeSocket(socket); } } catch (IOException x) { if (running) { log.error(sm.getString("endpoint.accept.fail"), x); } } catch (NullPointerException npe) { if (running) { log.error(sm.getString("endpoint.accept.fail"), npe); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString("endpoint.accept.fail"), t); } } state = AcceptorState.ENDED;
// Process the request from this socket try { SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket); wrapper.setKeepAliveLeft(getMaxKeepAliveRequests()); // During shutdown, executor may be null - avoid NPE if (!running) { return false; } getExecutor().execute(new SocketProcessor(wrapper)); } catch (RejectedExecutionException x) { log.warn("Socket processing request was rejected for:"+socket,x); return false; } catch (Throwable t) { ExceptionUtils.handleThrowable(t); // This means we got an OOM or similar creating a thread, or that // the pool and its queue are full log.error(sm.getString("endpoint.process.fail"), t); return false; } return true;
最后由协议处理器AbstractHttp11Processor.process(SocketWrapper<S> socketWrapper),去进行底层io操作,读取输入流构造http请求。
RequestInfo rp = request.getRequestProcessor(); rp.setStage(org.apache.coyote.Constants.STAGE_PARSE); // Setting up the I/O setSocketWrapper(socketWrapper); getInputBuffer().init(socketWrapper, endpoint); getOutputBuffer().init(socketWrapper, endpoint);
Http协议的解码由http11.InternalInputBuffer实现
8 为了提供服务器性能,tomcat还提供基于java nio的NioEndpoint,还有基于APR(Apache Portable Runtime)技术,直接和操作系统交互,更好的集成一些现有的本地服务器技术。web 服务器通过ajp协议和应用服务器通信。
参考(jni http://baike.baidu.com/view/1272329.htm;
http://hi.baidu.com/ugo5/blog/item/96710efe34aa7e5cd7887de2.html
http://www.itpub.net/thread-1064918-1-1.html
http://guojuanjun.blog.51cto.com/277646/688559
http://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html)
9 tomcat使用自定义的classloader加载servlet,是出于安全考虑。如果采用系统类加载器统一加载,那么运行的恶意servlet就可以访问到classpath中所有的类库。而使用自定义class loader加载servlet类就可以做到代码访问的隔离,除非应用允许,否则一个类只能访问到同一命名空间的类(即同一个类加载器加载的class)。
另外一个原因是为了支持动态类加载,例如reload功能,支持热部署。可以感知到web lib下的class文件修改,自动重新加载,这是系统类加载器做不到的。
To specify certain rules in loading classes.
To cache the previously loaded classes.
To pre-load classes so they are ready to use.
tomcat控制一个webapp只能访问到它自己的WEB-INF/lib 下面的class,不能访问tomcat所运行的jvm下面的classpath(由系统类加载器加载的)。
它的默认class loader是WebappLoader,由下面步骤完成隔离加载和自动热部署的效果
Creating a class loader
Setting repositories
Setting the class path
Setting permissions
Starting a new thread for auto-reload.
10 servlet依据是否实现SingleThreadModel,分为单线程模型和多线程模型。实现SingleThreadModel(该模型的含义具有一定误导性,已经被废弃)的servlet的,容器会做同步,保证任意时刻只会有一个线程访问servlet(为了保证性能,容器会创建一个servlet实例池)。相反,另一种servlet的,容器将允许多线程访问,如果有需要,业务要自己做好同步。
由于servlet具有两种含义的线程,所以wrapper需要根据自己所表示的servlet的线程安全性,在分配servlet的时候做更多的处理逻辑。
StandardWrapper.allocate()
@Override public Servlet allocate() throws ServletException { // If we are currently unloading this servlet, throw an exception if (unloading) throw new ServletException (sm.getString("standardWrapper.unloading", getName())); boolean newInstance = false; // If not SingleThreadedModel, return the same instance every time if (!singleThreadModel) { // Load and initialize our instance if necessary if (instance == null) { synchronized (this) { if (instance == null) { try { if (log.isDebugEnabled()) log.debug("Allocating non-STM instance"); instance = loadServlet(); if (!singleThreadModel) { // For non-STM, increment here to prevent a race // condition with unload. Bug 43683, test case // #3 newInstance = true; countAllocated.incrementAndGet(); } } catch (ServletException e) { throw e; } catch (Throwable e) { ExceptionUtils.handleThrowable(e); throw new ServletException (sm.getString("standardWrapper.allocate"), e); } } } } if (!instanceInitialized) { initServlet(instance); } if (singleThreadModel) { if (newInstance) { // Have to do this outside of the sync above to prevent a // possible deadlock synchronized (instancePool) { instancePool.push(instance); nInstances++; } } } else { if (log.isTraceEnabled()) log.trace(" Returning non-STM instance"); // For new instances, count will have been incremented at the // time of creation if (!newInstance) { countAllocated.incrementAndGet(); } return (instance); } } synchronized (instancePool) { while (countAllocated.get() >= nInstances) { // Allocate a new instance if possible, or else wait if (nInstances < maxInstances) { try { instancePool.push(loadServlet()); nInstances++; } catch (ServletException e) { throw e; } catch (Throwable e) { ExceptionUtils.handleThrowable(e); throw new ServletException (sm.getString("standardWrapper.allocate"), e); } } else { try { instancePool.wait(); } catch (InterruptedException e) { // Ignore } } } if (log.isTraceEnabled()) log.trace(" Returning allocated STM instance"); countAllocated.incrementAndGet(); return instancePool.pop(); } }
11 tomcat的启动类位于stratup包下面,有Bootstrap创建一个Catalina实例,并调用Catalina的处理方法。之所以分为2个类,是因为在不同的系统下面,会有多个Bootstrap的实现。bin目录下面的shell程序就是用来启动tomcat.
12 StringManager,当tomcat发生异常行为时,需要记录错误信息。tomcat采用properties文件来维护错误文案,每个package下面都有自己的一个properties文件。当需要获取错误文案时,直接调用StringManager.getManager(String packageName)即可,这边使用了单例模式,每个package下面共享一个manager.
/** * Get the StringManager for a particular package. If a manager for * a package already exists, it will be reused, else a new * StringManager will be created and returned. * * @param packageName The package name */ public static final synchronized StringManager getManager(String packageName) { StringManager mgr = managers.get(packageName); if (mgr == null) { mgr = new StringManager(packageName); managers.put(packageName, mgr); } return mgr; }