《How tomcat works》读书笔记_容器(3)

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方法,如果了解了上述内容,那么接下来的也就不难理解了:

  1. 一个容器拥有一个管道,容器的invoke方法将会调用管道的invoke方法
  2. 管道的invoke方法会先请求所有添加到这个容器的Value,最后调用管道自己的基本Value
  3. 在一个Wrapper容器中,基本Value负责加载相关的servlet类,并且响应request请求
  4. 在一个拥有子容器的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
------------------------------------

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值