本文接下来会介绍Host容器和Engine容器,在tomcat的实际部署中,总是会使用一个Host容器;本文介绍Host接口和Engine接口及其相关类
Host容器是org.apache.catalina.Host接口的实例,Host接口继承自Container接口, 其定义如下
public interface Host extends Container { public static final String ADD_ALIAS_EVENT = "addAlias"; public static final String REMOVE_ALIAS_EVENT = "removeAlias"; public String getAppBase(); public void setAppBase(String appBase); public boolean getAutoDeploy(); public void setAutoDeploy(boolean autoDeploy); public void addDefaultContext(DefaultContext defaultContext); public DefaultContext getDefaultContext(); public String getName(); public void setName(String name); public void importDefaultContext(Context context); public void addAlias(String alias); public String[] findAliases(); public Context map(String uri); public void removeAlias(String alias); }
该接口中比较重要的方法是map()方法,该方法返回一个用来处理引入的HTTP请求的Context容器的实例,该方法的具体实现在StandardHost类中
在tomcat中org.apache.catalina.core.StandardHost类是Host接口的标准实现,该类继承自org.apache.catalina.core.ContainerBase类,实现了Host和Deployer接口
与StandardContext类和StandardWrapper类相似,StandardHost类的构造函数会将一个基础阀的实例添加到其管道对象中:
public StandardHost() { super(); pipeline.setBasic(new StandardHostValve()); }
基础阀是org.apache.catalina.core.StandardHostValve类的实例
当调用其start()方法时,StandardHost实例会添加两个阀,分别为ErrorReportValve类和ErrorDispatcherValve类的实例,这两个阀均位于org.apache.catalina.valves包下
public synchronized void start() throws LifecycleException { // Set error report valve if ((errorReportValveClass != null) && (!errorReportValveClass.equals(""))) { try { Valve valve = (Valve) Class.forName(errorReportValveClass) .newInstance(); addValve(valve); } catch (Throwable t) { log(sm.getString ("standardHost.invalidErrorReportValveClass", errorReportValveClass)); } } // Set dispatcher valve addValve(new ErrorDispatcherValve()); super.start(); }
每当引入一个HTTP请求,都会调用Host实例的invoke()方法,这里是StandardHost的父类ContainerBase类的invoke()方法,而ContainerBase类的invoke()方法会调用StandardHost实例的基础阀StandardHostValve实例的invoke()方法;StandardHostValve类的invoke()方法会调用StandardHost类的map()方法来获取相应的Context实例来处理HTTP请求
public Context map(String uri) { if (debug > 0) log("Mapping request URI '" + uri + "'"); if (uri == null) return (null); // Match on the longest possible context path prefix if (debug > 1) log(" Trying the longest context path prefix"); Context context = null; String mapuri = uri; while (true) { context = (Context) findChild(mapuri); if (context != null) break; int slash = mapuri.lastIndexOf('/'); if (slash < 0) break; mapuri = mapuri.substring(0, slash); } // If no Context matches, select the default Context if (context == null) { if (debug > 1) log(" Trying the default context"); context = (Context) findChild(""); } // Complain if no Context has been selected if (context == null) { log(sm.getString("standardHost.mappingError", uri)); return (null); } // Return the mapped Context (if any) if (debug > 0) log(" Mapped to context '" + context.getPath() + "'"); return (context); }
在tomcat4中, StandardHost的父类ContainerBase类会调用其addDefaultMapper()方法创建一个默认映射器,默认映射器的类型由mapperClass属性的值决定
protected void addDefaultMapper(String mapperClass) { // Do we need a default Mapper? if (mapperClass == null) return; if (mappers.size() >= 1) return; // Instantiate and add a default Mapper try { Class clazz = Class.forName(mapperClass); Mapper mapper = (Mapper) clazz.newInstance(); mapper.setProtocol("http"); addMapper(mapper); } catch (Exception e) { log(sm.getString("containerBase.addDefaultMapper", mapperClass), e); } }
变量mapperClass的值定义在StandardHst类中
private String mapperClass =
"org.apache.catalina.core.StandardHostMapper";
在tomcat4中,StandardHost类的start()方法会在方法末尾调用addDefaultMapper()方法,确保默认映射器的创建完成
当然,StandardHostMapper类中最重要的方法是map()方法,下面是map()方法的实现
public Container map(Request request, boolean update) { // Has this request already been mapped? if (update && (request.getContext() != null)) return (request.getContext()); // Perform mapping on our request URI String uri = ((HttpRequest) request).getDecodedRequestURI(); Context context = host.map(uri); // Update the request (if requested) and return the selected Context if (update) { request.setContext(context); if (context != null) ((HttpRequest) request).setContextPath(context.getPath()); else ((HttpRequest) request).setContextPath(null); } return (context); }
注意,这里map()方法仅仅是简单地调用了Host实例的map()方法
org.apache.catalina.core.StandardHostValve类是StandardHost实例的基础阀,当有引入的HTTP请求时,会调用StandardHostValve类的invoke()方法对其进行处理
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException { // Validate the request and response object types if (!(request.getRequest() instanceof HttpServletRequest) || !(response.getResponse() instanceof HttpServletResponse)) { return; // NOTE - Not much else we can do generically } // Select the Context to be used for this Request StandardHost host = (StandardHost) getContainer(); Context context = (Context) host.map(request, true); if (context == null) { ((HttpServletResponse) response.getResponse()).sendError (HttpServletResponse.SC_INTERNAL_SERVER_ERROR, sm.getString("standardHost.noContext")); return; } // Bind the context CL to the current thread Thread.currentThread().setContextClassLoader (context.getLoader().getClassLoader()); // Update the session last access time for our session (if any) HttpServletRequest hreq = (HttpServletRequest) request.getRequest(); String sessionId = hreq.getRequestedSessionId(); if (sessionId != null) { Manager manager = context.getManager(); if (manager != null) { Session session = manager.findSession(sessionId); if ((session != null) && session.isValid()) session.access(); } } // Ask this Context to process this request context.invoke(request, response); }
在tomcat4中,invoke()方法会调用StandardHost实例的map()方法获取一个相应的Context实例;然后获取与该request对象相关联的session对象,并调用其access()方法,access()方法会修改session对象的最后访问时间;最后调用Context实例的invoke()来处理HTTP请求
接下来描述Engine容器,Engine容器是org.apache.catalina.Engine接口的实例,Engine容器也就是Tomcat的servlet引擎
public interface Engine extends Container { public String getDefaultHost(); public void setDefaultHost(String defaultHost); public String getJvmRoute(); public void setJvmRoute(String jvmRouteId); public Service getService(); public void setService(Service service); public void addDefaultContext(DefaultContext defaultContext); public DefaultContext getDefaultContext(); public void importDefaultContext(Context context); }
在Engine容器中,可以设置一个默认的Host容器或一个默认的Context容器,注意,Engine容器可以与一个服务实例相关联
org.apache.catalina.core.StandardEngine类是Engine接口的标准实现,在实例化的时候,StandardEngine类会添加一个基础阀
public StandardEngine() { super(); pipeline.setBasic(new StandardEngineValve()); }
org.apache.catalina.core.StandardEngineValve类是StandardEngine容器的基础阀,下面是它的invoke()方法的实现代码
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException { // Validate the request and response object types if (!(request.getRequest() instanceof HttpServletRequest) || !(response.getResponse() instanceof HttpServletResponse)) { return; // NOTE - Not much else we can do generically } // Validate that any HTTP/1.1 request included a host header HttpServletRequest hrequest = (HttpServletRequest) request; if ("HTTP/1.1".equals(hrequest.getProtocol()) && (hrequest.getServerName() == null)) { ((HttpServletResponse) response.getResponse()).sendError (HttpServletResponse.SC_BAD_REQUEST, sm.getString("standardEngine.noHostHeader", request.getRequest().getServerName())); return; } // Select the Host to be used for this Request StandardEngine engine = (StandardEngine) getContainer(); Host host = (Host) engine.map(request, true); if (host == null) { ((HttpServletResponse) response.getResponse()).sendError (HttpServletResponse.SC_BAD_REQUEST, sm.getString("standardEngine.noHost", request.getRequest().getServerName())); return; } // Ask this Host to process this request host.invoke(request, response); }
在验证了request和response对象的类型后,invoke()方法会通过调用Engine实例的map()方法获取Host对象;得到Host对象以后,调用其invoke()方法处理请求
---------------------------------------------------------------------------
本系列How Tomcat Works系本人原创
转载请注明出处 博客园 刺猬的温驯
本人邮箱: chenying998179#163.com (#改为@)