Tomcat工作原理(二)一个简单的Servlet容器

本章主要通过两个应用讲述如何编写自己的Servler容器,第一个比较见到,只是为了理解一下Servlet容器是如何工作的。然后引出较为复杂的第二个例子。

javax.servlet.Servlet 接口

先来看一个常见的Servlet类:

//how tomcat works 一书出版时间较早,此书中使用的接口可能和日常的有所出入,但是原理上是一样的。
public class PrimitiveServlet implements Servlet {
    @Override
    public void destroy() {
        System.out.println("destroy");
    }
    @Override
    public ServletConfig getServletConfig() {
        return null;
    }
    @Override
    public String getServletInfo() {
        return null;
    }
    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("init");
    }
    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        System.out.println("from service");
        PrintWriter out = response.getWriter();
        out.println("Hello. Roses are red.");
        out.print("Violets are blue.");
    }
}

要理解这些代码,需要对javax.servlet.Servlet Interface 有一定的了解,该接口中包含有下面五个方法:

public void init(ServletConfig config) throws ServletException
public void service(ServletRequest request, ServletResponse response)
throws ServletException, java.io.IOException
public void destroy()
public ServletConfig getServletConfig()
public java.lang.String getServletInfo()

在这些方法中,init,service,destroy方法是Servlet的生命周期方法,当Servlet类被初始化后会立即调用init方法,执行一些初始化的操作,表明Servlet已经被放置到服务中。在Servlet接受request请求之时,init必须已经执行完毕。
当请求Servlet时,service方法就会被调用。servlet容器包含javax.servlet.ServletRequest 对象和javax.servlet.ServletResponse 对象(关于这两个对象和Servlet的实质可以参考上一章的代码)。ServletRequest 包含有HTTP请求的一些信息,而ServletResponse 包含有响应的一些信息。
将servlet实例从服务中移除之前会调用destroy方法,这一般是在servlet容器被关闭或者servlet容器需要更多的内存空间时被执行。这个方法的调用要求所有包含有service方法的线程都被退出。destroy方法存在使得servlet可以进行一些清理资源的操作。

应用1

从上面的例子可以看到,对于每一个HTTP请求,Servlet容器做了下面的一些事情:

  • servlet被请求的时候,加载Servlet类并调用init方法(执行一次)
  • 对于每一个请求,构造javax.servlet.ServletRequest 对象和javax.servlet.ServletResponse 对象
  • 执行service方法,传递ServletRequestServletResponse 对象
  • servlet类被关闭时,调用destroy方法,移除servlet类

下面要编写的servlet容器功能并不完全,他只能完成一个简单的servlet的容器的功能,并且并不调用init和destroy方法。他的功能如下:

  • 等待HTTP请求
  • 构建Request对象和Response对象
  • 如果Request请求的是静态资源,创建StaticResourceProcessor实例,传入Request和Response对象
  • 如果Request请求的是Servlet,加载servlet类调用service方法并传入Request和Response对象
    注:在此servlet容器中,每次request请求servlet类都会被加载。

第一个应用包含有6个类:

  • HttpServer1
  • Request
  • Response
  • StaticResourceProcessor
  • ServletProcessor1
  • Constants

uml结构如下:

这里写图片描述

应用的入口(main方法)在HttpServer1中,HttpServer1的实现和第一章中HttpServer类 类似,只是HttpServer1不仅可以处理静态文件的请求,也可以处理servlet的请求
代码:

public class HttpServer1 {
  // shutdown command
  private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
  // the shutdown command received
  private boolean shutdown = false;
  public static void main(String[] args) {
    HttpServer1 server = new HttpServer1();
    server.await();
  }
  public void await() {
    ServerSocket serverSocket = null;
    int port = 8080;
    try {
      serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
    }
    catch (IOException e) {
      e.printStackTrace();
      System.exit(1);
    }
    // Loop waiting for a request
    while (!shutdown) {
      Socket socket = null;
      InputStream input = null;
      OutputStream output = null;
      try {
        socket = serverSocket.accept();
        input = socket.getInputStream();
        output = socket.getOutputStream();
        // create Request object and parse
        Request request = new Request(input);
        request.parse();
        // create Response object
        Response response = new Response(output);
        response.setRequest(request);
        //判断uri是以什么开头的
        if (request.getUri().startsWith("/servlet/")) {
          ServletProcessor1 processor = new ServletProcessor1();
          processor.process(request, response);
        }
        else {
          StaticResourceProcessor processor = new StaticResourceProcessor();
          processor.process(request, response);
        }

        // Close the socket
        socket.close();
        //check if the previous URI is a shutdown command
        shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
      }
      catch (Exception e) {
        e.printStackTrace();
        System.exit(1);
      }
    }
  }
}

Request类代码:

public class Request implements ServletRequest {
  private InputStream input;
  private String uri;
  public Request(InputStream input) {
    this.input = input;
  }
  public String getUri() {
    return uri;
  }
  private String parseUri(String requestString) {
    int index1, index2;
    index1 = requestString.indexOf(' ');
    if (index1 != -1) {
      index2 = requestString.indexOf(' ', index1 + 1);
      if (index2 > index1)
        return requestString.substring(index1 + 1, index2);
    }
    return null;
  }
  public void parse() {
    // Read a set of characters from the socket
    StringBuffer request = new StringBuffer(2048);
    int i;
    byte[] buffer = new byte[2048];
    try {
      i = input.read(buffer);
    }
    catch (IOException e) {
      e.printStackTrace();
      i = -1;
    }
    for (int j=0; j<i; j++) {
      request.append((char) buffer[j]);
    }
    System.out.print(request.toString());
    uri = parseUri(request.toString());
  }
  //....其他来自父类的抽象使用空实现
}

Response类:

public class Response implements ServletResponse {
  private static final int BUFFER_SIZE = 1024;
  Request request;
  OutputStream output;
  PrintWriter writer;
  public Response(OutputStream output) {
    this.output = output;
  }
  public void setRequest(Request request) {
    this.request = request;
  }
  /* This method is used to serve a static page */
  public void sendStaticResource() throws IOException {
    byte[] bytes = new byte[BUFFER_SIZE];
    FileInputStream fis = null;
    try {
      /* request.getUri has been replaced by request.getRequestURI */
      File file = new File(Constants.WEB_ROOT, request.getUri());
      fis = new FileInputStream(file);
      /*
         HTTP Response = Status-Line
           *(( general-header | response-header | entity-header ) CRLF)
           CRLF
           [ message-body ]
         Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
      */
      int ch = fis.read(bytes, 0, BUFFER_SIZE);
      while (ch!=-1) {
        output.write(bytes, 0, ch);
        ch = fis.read(bytes, 0, BUFFER_SIZE);
      }
    }
    catch (FileNotFoundException e) {
      String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
        "Content-Type: text/html\r\n" +
        "Content-Length: 23\r\n" +
        "\r\n" +
        "<h1>File Not Found</h1>";
      output.write(errorMessage.getBytes());
    }
    finally {
      if (fis!=null)
        fis.close();
    }
  }
  public PrintWriter getWriter() throws IOException {
    // autoflush is true, println() will flush,
    // but print() will not.
    writer = new PrintWriter(output, true);
    return writer;
  }
  //......其他父类中抽象方法为空实现
}

StaticResourceProcessor 访问静态文件处理器:

public class StaticResourceProcessor {

  public void process(Request request, Response response) {
    try {
      //和上章一样,这里处理静态文件使用response中的方法
      response.sendStaticResource();
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }
}

ServletProcessor1 处理对servlet的请求:

public class ServletProcessor1 {
  public void process(Request request, Response response) {
    String uri = request.getUri();
    String servletName = uri.substring(uri.lastIndexOf("/") + 1);
    URLClassLoader loader = null;

    try {
      // 创建URL类加载器
      URL[] urls = new URL[1];
      URLStreamHandler streamHandler = null;
      File classPath = new File(Constants.WEB_ROOT);
      // getCanonicalPath()返回抽象路径名的规范路径名字符串
      String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
      urls[0] = new URL(null, repository, streamHandler);
      loader = new URLClassLoader(urls);
    }
    catch (IOException e) {
      System.out.println(e.toString() );
    }
    Class myClass = null;
    try {
      myClass = loader.loadClass(servletName);
    }
    catch (ClassNotFoundException e) {
      System.out.println(e.toString());
    }

    Servlet servlet = null;

    try {
      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());
    }
  }
}

Constants类:

public class Constants {
  public static final String WEB_ROOT =
    System.getProperty("user.dir") + File.separator  + "webroot";
}

启动HttpServer1:
在浏览器中输入http://localhost:8080/index.html 将显示项目目录webroot下的index.html
在浏览器中输入http://localhost:8080/servlet/PrimitiveServlet 可以对servlet进行访问,由此次应用简单,并且使用的是直接加载类,所以需在此路下先编译PrimitiveServlet.java。

应用2:

在上一个应用中有一个较为严重的问题,就是向上转型的问题:

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

这段代码对应用的安全构成产生了一定的影响,如果知道此容器内部工作原理的程序员,可能会将此ServletRequest 对象或者ServletResponse 对象再次向下转型成为Request或者Response对象 并调用里面的public方法(parse或者sendStaticResource) 这将导致程序有可能崩溃。

可以通过新建2个类来解决次问题:
这里写图片描述

public class RequestFacade implements ServletRequest {

  private ServletRequest request = null;
  //构造中传入   request
  public RequestFacade(Request request) {
    this.request = request;
  }

  /* ServletRequest其他抽象方法*/
  public Object getAttribute(String attribute) {
    return request.getAttribute(attribute);
  }
   public Enumeration getAttributeNames() {
    return request.getAttributeNames();
  }
  //.......其他方法类似 就不一一列举了
}
public class ResponseFacade implements ServletResponse {

  private ServletResponse response;
  public ResponseFacade(Response response) {
    this.response = response;
  }

  public void flushBuffer() throws IOException {
    response.flushBuffer();
  }

  public int getBufferSize() {
    return response.getBufferSize();
  }
  //......其他方法类似 就不一一列举
}

上述有问题的代码使用此段代码替换:

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

RequestFacadeResponseFacade即可解决此问题 方法都是来自父类,无论是向上还是父类向下转型都是较为安全的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值