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/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);
}