Tomcat源码解析(八):Host

host的作用是啥?

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

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

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

主流程

host的主要内容

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

public class StandardHost
extends ContainerBase
implements 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/Primitivehttp://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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值