Context应用
在前面的程序中,我们了解了如何仅仅使用wrapper来部署一个简单的Web应用。在前面的程序中只有一个servlet,但是有些Web应用需要的可能不止是一个servlet,大多数的Web应用需要更多的servlet。在这样的应用中,你需要的是另外一种容器而不再是wrapper容器,这种容器就是Context。
这个应用将论述如何使用Context来包容两个Wrapper,每个Wrapper对应一个servlet类。因为有两个wrapper,所以你需要一个Mapper,Mapper组件是用来帮助容器(这里就是指Context)选择一个可以处理指定request请求的子容器。在这个应用中,我们的Mapper类是ex05.pyrmont.core.SimpleContextMapper,它实现了org.apache.catalina.Mapper接口。一个容器可以有多个Mapper来适应多种不同的协议。一个Mapper支持一种request请求协议。举例来说,就是一个容器可能有一个HTTP协议的Mapper和一个HTTPS协议的Mapper。
package org.apache.catalina;
public interface Mapper {
public Container getContainer();
public void setContainer(Container container);
public String getProtocol();
public void setProtocol(String protocol);
public Container map(Request request, boolean update);
}
getContainer方法获取跟这个Mapper所关联的容器,而setContainer方法用于关联一个容器给Mapper。getProtocol返回这个Mapper所负责的协议名称,setProtocol设置该Mapper所需负责的协议名称。map方法返回一个可以处理指定request请求的子容器。
下面是本章程序的类图:
SimpleContext代表了一个Context容器,它将SimpleContextMapper当作是它的Mapper,以SimpleContextValve作为它的基本Value。两个Value:ClientIPLoggerValve和HeaderLoggerValve都被添加到了Context中(上例中,两个Value添加到了Wrapper中)。两个Wrapper,类型都是SimpleWrapper,被添加为Context的子容器。wrapper使用SimpleWrapperValve为其基本Value,这个例子中wrapper中除了基本Value再没有其它的Value。
在这个应用中,使用了同上例中一样的Loader和两个Value。唯一不同的是,Loader和两个Value在这里它们只与Context关联,而非是Wrapper了。这样的话,两个Wrapper也可以使用这个Loader了(在前面已经论述过,在Wrapper的getLoader方法,如果Wrapper关联了Loader就直接返回,如果没有关联则返回其父容器的Loader。Context是其父容器,父容器设置了Loader,那么它的子容器也可以使用这个Loader)。Context将会被设置成connector的容器,这样每当connector收一个HTTP请求时它都会调用Context的invoke方法,如果了解了上述内容,那么接下来的也就不难理解了:
- 一个容器拥有一个管道,容器的invoke方法将会调用管道的invoke方法
- 管道的invoke方法会先请求所有添加到这个容器的Value,最后调用管道自己的基本Value
- 在一个Wrapper容器中,基本Value负责加载相关的servlet类,并且响应request请求
- 在一个拥有子容器的Context容器中,它的基本Value使用Mapper来查找哪个请求应该被哪个子容器来处理。如果找到了这个子容器,直接调用这个子容器的invoke方法。然后再返回第一步。
现在我们按顺序来看实现的过程:
public void invoke(Request request, Response response)
throws IOException, ServletException {
pipeline.invoke(request, response);
}
SimpleContext类的invoke方法调用了管道的Invoke方法。
public void invoke(Request request, Response response)
throws IOException, ServletException {
// Invoke the first Valve in this pipeline for this request
(new SimplePipelineValveContext()).invokeNext(request, response);
}
管道是一个SimplePipeline类,上面是它的invoke方法实现。
如前面“管道任务”中介绍,代码会请求所有添加到它里面的Value,然后调用基本Value的invoke方法。
Wrapper wrapper = null;
try {
wrapper = (Wrapper) context.map(request, true);
} catch (IllegalArgumentException e) {
badRequest(requestURI,
(HttpServletResponse) response.getResponse());
return;
}
SimpleContextValve类为一个基本Value,在它的invoke方法中,它使用Cotext容器的Mapper去找一个wrapper容器。
如果找到了这个Wrapper,就直接调用它的invoke方法。
在这个应用中,Wrapper容器由SimpleWrapper类来表示,下面是SimpleWrapper类的invoke方法,它同Context容器中的invoke方法是完全相同的。
public void invoke(Request request, Response response)
throws IOException, ServletException {
pipeline.invoke(request, response);
}
管道是SimplePipeline的一个实例。在这个应用中,wrapper只有一个SimpleWrapperValve实例的基本Value,除此之外没有其它任何Value。wrapper的管道调用SimpleWrapperValve类的invoke方法分配一个servlet关调用这个servlet的service方法。
注意到,这个应用的Wrapper容器中关没有关联一个Loader,但Context容器中关联了。因此,SimpleWrapper类中的getLoader返回的其实是其父容器Context中的Loader。
SimpleContextValve
这个类是作为Context容器SimpleContext类的一个基本Value,其主要的方法就是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
}
// Disallow any direct access to resources under WEB-INF or META-INF
HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
String contextPath = hreq.getContextPath();
String requestURI = ((HttpRequest) request).getDecodedRequestURI();
String relativeURI = requestURI.substring(contextPath.length())
.toUpperCase();
Context context = (Context) getContainer();
// Select the Wrapper to be used for this Request
Wrapper wrapper = null;
try {
wrapper = (Wrapper) context.map(request, true);
} catch (IllegalArgumentException e) {
badRequest(requestURI,
(HttpServletResponse) response.getResponse());
return;
}
if (wrapper == null) {
notFound(requestURI, (HttpServletResponse) response.getResponse());
return;
}
// Ask this Wrapper to process this Request
response.setContext(context);
wrapper.invoke(request, response);
}
SimpleContextMapper
org.apache.catalina.Mapper实现了org.apache.catalina.Mapper接口,它与一个SimpleContext关联。它的setContainer方法中,如果传入的不是一个SimpleContext的类型,会抛出一个IllegalArgumentException异常。
public void setContainer(Container container) {
if (!(container instanceof SimpleContext))
throw new IllegalArgumentException("Illegal type of container");
context = (SimpleContext) container;
}
在这个类中,最重要的方法是map方法。这个方法返回一个子容器来处理相应的request请求。
public Container map(Request request, boolean update) {
// Identify the context-relative URI to be mapped
String contextPath = ((HttpServletRequest) request.getRequest())
.getContextPath();
String requestURI = ((HttpRequest) request).getDecodedRequestURI();
String relativeURI = requestURI.substring(contextPath.length());
// Apply the standard request URI mapping rules from the specification
Wrapper wrapper = null;
String servletPath = relativeURI;
String pathInfo = null;
String name = context.findServletMapping(relativeURI);
if (name != null)
wrapper = (Wrapper) context.findChild(name);
return (wrapper);
}
这个方法传递了两个参数,一个是request对象,另一个是boolean值。当前方法的实现忽略了第二个参数。在方法内,从request对象中获取context path;然后调用context容器的findServletMapping方法获取一个和这个路径关联的名称;如果找到了这个名称,则利用它调用context容器的findChild方法来获取一个它的子容器,也就是一个Wrapper实例。
SimpleContext
SimpleContext是这个应用中Context容器的实现类。它是和connector关联的主容器。但是,执行每个单独servlet的任务是交给Wrapper容器来实现的。这个应用有两个servlet:PrimitiveServlet和ModernServlet,所以也就有两个wrapper。每一个wrapper都有一个名称:PrimitiveServlet的wrapper名称为Primitive,而ModernServlet的wrapper名称为Modern。Context容器SimpleContext通过映射请求URL与wrapper的名称来确定使用哪个wrapper子容器来响应这个请求。
当然,你可以使用多种模式来对应一个servlet。你只需要添加这些模式即可。
Container和Context接口中有很多方法需要SimpleContext来实现,大部分方法是置空的,只有那些涉及到子容器映射的方法给出了实现,这些实现了方法如下:
- addServletMapping:增加一个URL模式和wrapper名称的键值对,你可以增加每一个可以被用来请求指定名称wrapper的URL模式。
- findServletMapping:通过给出的URL模式来获取一个与之匹配的wrapper,这个方法主要用于查找对于给定的URL模式应该请求哪个wrapper。如果查找的URL模式还没有通过addServletMapping注册,那么直接返回null.
- addMapper:为Context容器加一个Mapper,SimpleContext类中变量mapper和mappers来持有增加的Mapper。mapper是默认的Mapper,mappers持有所有的SimpleContext中的Mapper。第一个添加到SimpleContext容器中的Mapper,为默认的Mapper。
- findMapper:找到正确的Mapper,这里直接返回默认Mapper。
- map:返回可以处理request请求的wrapper。
Bootstrap2
public final class Bootstrap2 {
public static void main(String[] args) {
HttpConnector connector = new HttpConnector();
Wrapper wrapper1 = new SimpleWrapper();
wrapper1.setName("Primitive");
wrapper1.setServletClass("PrimitiveServlet");
Wrapper wrapper2 = new SimpleWrapper();
wrapper2.setName("Modern");
wrapper2.setServletClass("ModernServlet");
Context context = new SimpleContext();
context.addChild(wrapper1);
context.addChild(wrapper2);
Valve valve1 = new HeaderLoggerValve();
Valve valve2 = new ClientIPLoggerValve();
((Pipeline) context).addValve(valve1);
((Pipeline) context).addValve(valve2);
Mapper mapper = new SimpleContextMapper();
mapper.setProtocol("http");
context.addMapper(mapper);
Loader loader = new SimpleLoader();
context.setLoader(loader);
// context.addServletMapping(pattern, name);
context.addServletMapping("/Primitive", "Primitive");
context.addServletMapping("/Modern", "Modern");
connector.setContainer(context);
try {
connector.initialize();
connector.start();
// make the application wait until we press a key.
System.in.read();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行程序
运行第一个Servlet
http://localhost:8080/Primitive
Console端:
from service
Client IP Logger Valve
0:0:0:0:0:0:0:1
------------------------------------
Header Logger Valve
host:localhost:8080
connection:keep-alive
cache-control:max-age=0
accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
user-agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36
accept-encoding:gzip,deflate,sdch
accept-language:zh-CN,zh;q=0.8
------------------------------------
from service是PrimitiveServlet中init方法打印的字符
运行第二个Servlet
http://localhost:8080/Modern
Console端:
ModernServlet -- init
Client IP Logger Valve
0:0:0:0:0:0:0:1
------------------------------------
Header Logger Valve
host:localhost:8080
connection:keep-alive
accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
user-agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36
accept-encoding:gzip,deflate,sdch
accept-language:zh-CN,zh;q=0.8
------------------------------------