Tomcat源码解析(八):Host

来源:互联网 发布:移动网络代理服务器 编辑:程序博客网 时间:2024/06/04 19:57

host的作用是啥?

之前我们学习了context,它表示一个独立的web应用。当我们一台机器上只有一个web应用的时候,只需要把context当做最上层的容器就可以了。可是通常我们希望在一个机器上部署多个应用,就需要有多个context。这时候怎么将不同的请求匹配到不同的context呢?

就由host来解决这个问题啦。

context管理wrapper,同样地,host来管理context。

主流程

host的主要内容

看了host、StandardHost的代码之后,感觉跟context的内容差别不大。区别是实现了Deployer接口。

public class StandardHostextends ContainerBaseimplements Deployer, Host {// ----------------------------------------------------------- Constructors/** * Create a new StandardHost component with the default basic Valve. */public StandardHost() {    super();    pipeline.setBasic(new StandardHostValve());}/** * start()中利用它创建mapper * The Java class name of the default Mapper class for this Container. */private String mapperClass =    "org.apache.catalina.core.StandardHostMapper";/** * Start this host. * * start()比较简单,创建errorReportValve之后,就调用ContainerBase的start()。ContainerBase的start()中,会根据mapperClass创建mapper * @exception LifecycleException if this component detects a fatal error *  that prevents it from being started */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();}

host的主要功能是根据请求匹配到对应的context,那么map方法就是最重要的一个方法啦。我们看下主流程吧。

StandardHost的basic valve是StandardHostValve。request进入后,调用StandardHostValve的invoke方法:

public void invoke(Request request, Response response,                   ValveContext valveContext)    throws IOException, ServletException {    System.out.println("[StandardHostValve] invoke");    // 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    //调用host的map(request, true)方法,实际来自于ContainerBase    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);}

StandardHostValve.invoke()调用ContainerBase.map(request, true)方法,ContainerBase.map(request, true)获取mapper,调用mapper的map(Request request, boolean update)。

public Container map(Request request, boolean update) {    // Select the Mapper we will use    //先获取StandardHostMapper    Mapper mapper = findMapper(request.getRequest().getProtocol());    if (mapper == null)        return (null);    // Use this Mapper to perform this mapping    //调用StandardHostMapper.map(request, update)    return (mapper.map(request, update));}

StandardHostMapper.map调用的是host的map方法,囧~

public Container map(Request request, boolean update) {    System.out.println("[StandardHostMapper] map");    // 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);}

那么host的map()方法长啥样呢?

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);}/** * host根据name匹配context * Return the child Container, associated with this Container, with * the specified name (if any); otherwise, return <code>null</code> * * @param name Name of the child Container to be retrieved */public Container findChild(String name) {    if (name == null)        return (null);    synchronized (children) {       // Required by post-start changes        return ((Container) children.get(name));    }}

实际就是在children这个map中,根据request的uri来匹配。children在初始化的时候就有了,可是我都没有在Bootstrap中看到给context设置name呢。。

别急,原来在这里:

/** * Set the context path for this Context. * <p> * <b>IMPLEMENTATION NOTE</b>:  The context path is used as the "name" of * a Context, because it must be unique. * * @param path The new context path */public void setPath(String path) {    setName(RequestUtil.URLDecode(path));}

所以Bootstrap.class中调用context.setPath,就给context设置name了。

host配置小例子

之前都是一个context,看到可以放两个应用,当然要试一试啦~看下我的小例子吧~

首先写一个自己的servlet:

/** * Created by zhangguixian on 8/14/16 */public class MyServlet implements Servlet {    @Override    public void init(ServletConfig servletConfig) throws ServletException {    }    @Override    public ServletConfig getServletConfig() {        return null;    }    @Override    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {        PrintWriter writer = servletResponse.getWriter();        writer.println("hello app2~");        writer.println("sunny day @@");    }    @Override    public String getServletInfo() {        return null;    }    @Override    public void destroy() {    }}

然后在webapps路径下,建两个子路径:

webapps/app1/WEB-INF/classes下放ModernServlet.class和PrimitiveServlet.class,webapps/app2/WEB-INF/classes下放MyServlet.class。

最后看Bootstrap类的测试代码:

public final class Bootstrap1 {  public static void main(String[] args) {    //invoke: http://localhost:8080/app1/Primitive or http://localhost:8080/app1/Modern    System.setProperty("catalina.base", System.getProperty("user.dir"));    Connector connector = new HttpConnector();    //app1    Wrapper wrapper1 = new StandardWrapper();    wrapper1.setName("Primitive");    wrapper1.setServletClass("PrimitiveServlet");    Wrapper wrapper2 = new StandardWrapper();    wrapper2.setName("Modern");    wrapper2.setServletClass("ModernServlet");    Context context1 = new StandardContext();    // StandardContext's start method adds a default mapper    context1.setPath("/app1");    context1.setDocBase("app1");    context1.addChild(wrapper1);    context1.addChild(wrapper2);    Loader loader1 = new WebappLoader();    context1.setLoader(loader1);    //app2    Wrapper MyServletWrapper = new StandardWrapper();    MyServletWrapper.setName("MyServlet");    MyServletWrapper.setServletClass("MyServlet");    Context context2 = new StandardContext();    // StandardContext's start method adds a default mapper    context2.setPath("/app2");    context2.setDocBase("app2");//context2下servlet的路径    context2.addChild(MyServletWrapper);    LifecycleListener listener = new SimpleContextConfig();    ((Lifecycle) context1).addLifecycleListener(listener);    ((Lifecycle) context2).addLifecycleListener(listener);    //host    Host host = new StandardHost();    host.addChild(context1);    host.addChild(context2);    host.setName("localhost");    host.setAppBase("webapps");//host下servlet的逻辑。那么context2的路径就是/webapps/app2,它包括的servlet都在这个路径下。    Loader loader2 = new WebappLoader();    context2.setLoader(loader2);    // context.addServletMapping(pattern, name);    context1.addServletMapping("/Primitive", "Primitive");    context1.addServletMapping("/Modern", "Modern");    context2.addServletMapping("/MyServlet", "MyServlet");    connector.setContainer(host);    try {      connector.initialize();      ((Lifecycle) connector).start();      ((Lifecycle) host).start();      // make the application wait until we press a key.      System.in.read();      ((Lifecycle) host).stop();    }    catch (Exception e) {      e.printStackTrace();    }  }}

启动项目后,在浏览器输入http://localhost:8080/app1/Primitive或http://localhost:8080/app2/MyServlet就可以看到不同的效果啦~

Engine

host之上还有engine,看了下是用来管理多个虚拟主机,没太看懂~

在网上查了下资料,大致明白了一点。一个host表示一个虚拟主机,表示一个域名。一个engine下配置多个host,就表示可以在一台机器上配置多个域名(localhost、www.helloworld.com),而且完全独立互不干扰。

主要内容在StandardEngine的默认mapper StandardEngineMapper的map()中:

public Container map(Request request, boolean update) {    int debug = engine.getDebug();    // Extract the requested server name    String server = request.getRequest().getServerName();    if (server == null) {        server = engine.getDefaultHost();        if (update)            request.setServerName(server);    }    if (server == null)        return (null);    server = server.toLowerCase();    if (debug >= 1)        engine.log("Mapping server name '" + server + "'");    // Find the matching child Host directly    //根据request中的servername即服务器主机名匹配host    if (debug >= 2)        engine.log(" Trying a direct match");    Host host = (Host) engine.findChild(server);    // Find a matching Host by alias.  FIXME - Optimize this!    if (host == null) {        if (debug >= 2)            engine.log(" Trying an alias match");        Container children[] = engine.findChildren();        for (int i = 0; i < children.length; i++) {            String aliases[] = ((Host) children[i]).findAliases();            for (int j = 0; j < aliases.length; j++) {                if (server.equals(aliases[j])) {                    host = (Host) children[i];                    break;                }            }            if (host != null)                break;        }    }    // Trying the "default" host if any    if (host == null) {        if (debug >= 2)            engine.log(" Trying the default host");        host = (Host) engine.findChild(engine.getDefaultHost());    }    // Update the Request if requested, and return the selected Host    ;       // No update to the Request is required    return (host);}
0 0