《How Tomcat Works》第二章:简单的servlet容器

本章呢是讲解如何开发一个简单的servlet容器,首先所有的servlet必须要实现javax.servlet.Servlet接口,如下:

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

在这五个方法中,init(),service(),destroy()是生命周期方法,当servlet类被装载初始化之后,会调用init()方法一次,在收到请求之前,init()方法必须完成。每当对此servlet发起请求的时候,都会调用service()方法,并传入ServletRequest与ServletResponse实例,前者包括了Http请求信息,后者封装了servlet应答信息。
servlet container
简而言之,一个完整的servlet容器会对每个Http请求做如下工作:

  1. 当servlet被第一次请求时,加载servlet类,并调用init()方法(仅此一次)
  2. 为每次请求构建ServletRequest与ServletResponse实例
  3. 调用service方法,并传入前面那两个实例
  4. 当关闭servlet类时,调用destroy方法,释放资源

    下面来说一下图中的这些类,HttpServer1和第一章差不多,只不过多了一个判断:

...
//如果uri是/servlet开头,就调用ServletProcecessor1的process方法,然后调用servlet的service方法,如果不是就直接访问静态资源
if (request.getUri().startsWith("/servlet/")) {
          ServletProcessor1 processor = new ServletProcessor1();
          processor.process(request, response);
        }
        else {
          StaticResourceProcessor processor = new StaticResourceProcessor();
          processor.process(request, response);
        }
...

接下来是ServletProcecessor1的process方法

public void process(Request request, Response response) {

    String uri = request.getUri();
    String servletName = uri.substring(uri.lastIndexOf("/") + 1);
    URLClassLoader loader = null;

    try {
      // create a URLClassLoader
      URL[] urls = new URL[1];
      URLStreamHandler streamHandler = null;
      File classPath = new File(Constants.WEB_ROOT);
      // the forming of repository is taken from the createClassLoader method in
      // org.apache.catalina.startup.ClassLoaderFactory
      String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
      // the code for forming the URL is taken from the addRepository method in
      // org.apache.catalina.loader.StandardClassLoader class.
      urls[0] = new URL(null, repository, streamHandler);
      loader = new URLClassLoader(urls);
    }
    catch (IOException e) {
      System.out.println(e.toString() );
    }
    Class myClass = null;
    try {
      //根据serlet的名字获取其Class信息
      myClass = loader.loadClass(servletName);
    }
    catch (ClassNotFoundException e) {
      System.out.println(e.toString());
    }

    Servlet servlet = null;

    try {
      //实例化该类,并且调用其service方法
      servlet = (Servlet) myClass.newInstance();
      servlet.service((ServletRequest) request, (ServletResponse) response);
    }
    catch (Exception e) {
      System.out.println(e.toString());
    }
    catch (Throwable e) {
      System.out.println(e.toString());
    }

  }

该方法首先根据包的路径创建一个URLClassLoader,然后调用loadClass方法获取该类的Class信息(根据从uri获取的类名),再实例化该类,最后调用service方法,传入request,response实例。

仔细观察后会发现,这上面的代码有很严重的安全隐患:

 try {
      servlet = (Servlet) myClass.newInstance();
      servlet.service((ServletRequest) request, (ServletResponse) response);
    }

在这里,request与response都被强制向上转换成了ServletRequest与ServletResponse,并传入service方法,这也就意味着,在servlet内部,你能通过向下转换调用Request的process方法与Response的sendStaticResource方法(通过request与Response实例),在这两个方法是public的前提下。但是这两个方法又不能设置为private方法,因为外部需要调用这两个方法。怎么解决呢?
使用facade类(门面设计模式)
这里写图片描述
创建一个门面类,往构造器里传入Request实例,并向上转型为ServletRequest


public class RequestFacade implements ServletRequest {

  private ServletRequest request = null;

  public RequestFacade(Request request) {
    this.request = request;
  }
  ...
  }

然后ServletProcess中的process方法就写成这样:

  RequestFacade requestFacade = new RequestFacade(request);
    ResponseFacade responseFacade = new ResponseFacade(response);
    try {
      servlet = (Servlet) myClass.newInstance();
      servlet.service((ServletRequest) requestFacade, (ServletResponse) responseFacade);
    }

这样的话我们就构造了一个requestFacade实例传入service方法,那么Request中的parse方法就安全了(Response也一样)。

猜你喜欢

转载自blog.csdn.net/qq_19894073/article/details/80891263