• 探秘Tomcat——一个简易的Servlet容器


    即便再简陋的服务器也是服务器,今天就来循着书本的第二章来看看如何实现一个servlet容器。

    背景知识

      既然说到servlet容器这个名词,我们首先要了解它到底是什么。

    servlet

      相比你或多或少有所了解。servlet是用java编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。

    容器

      容器的概念很大,在这里可以理解为能够管理对象(servlet)的生命周期,对象与对象之间的依赖关系。

      基于对以上两个概念的解释,那么对于serelvet容器的概念也就不再那么陌生了。

    servlet容器

      就是创建、管理servlet规范中相关对象、生命周期的应用程序。

     

    Servlet接口

      servlet是一种编程规范,要实现servlet编程需要用到javax.servlet和javax.servlet.http。所有的servlet程序都需要实现或继承自实现了javax.servlet.servlet接口。

                                                                                                                                                                 

    Servlet接口的方法

    • init():servlet容器的初始化方法,该方法只会被调用一次;
    • service():不同于init只会触发一次,service在客户端请求后就会被调用。同时需要传入参数servletRequest和servletResponse。从字面意思就能知道,servletRequest携带了客户端发送的HTTP请求的信息,而servletResponse则用于封装servlet的响应信息。
    • destroy():当servlet实例调用完毕要被移除时,destroy方法将被调用。
    • getServletConfig():该方法用于取得<servlet> <init-param>配置的参数
    • getServletInfo():该方法提供有关servlet的信息,如作者、版本、版权。

     

    servlet容器的职责

    • 第一次调用servlet时,需要载入serlvet类并调用init方法;
    • 针对客户端的request请求,创建一个servletRequest对象和一个servletResponse对象;
    • 传参servletRequest和servletResponse,调用service方法;
    • 当关闭servlet类时,调用destroy方法。

     

     

    简陋的servlet容器

      之所以说是简陋的servlet容器,因为这里并没有实现servlet所有的方法,该容器只能支持很简单的servlet,也没有init方法和destroy方法。主要实现功能如下:

    • 等待HTTP请求;
    • 创建serlvetRequest和servletResponse对象;
    • 能够分别处理静态资源和servlet,当客户端请求静态资源时,则调用StaticResourceProcessor对象的process方法;当请求为serlvet则载入请求的servlet类并调用service方法。

     

    主要包括6个类

    • HttpServer1:程序的入口,负责创建Request和Response对象,并根据HTTP请求类型将其转给相应的处理器处理;
    • Request:用于封装客户端HTTP请求信息;
    • Response:用于封装服务器响应信息;
    • StaticResourceProcessor:静态资源处理器;
    • ServletProcessor1:servlet处理器;
    • Constants:用于定义一些常量,如WEB_ROOT

     

    HttpServer1

    package day0522;
    
    import java.net.Socket;
    import java.net.ServerSocket;
    import java.net.InetAddress;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.IOException;
    
    public class HttpServer1 {
    
      /** WEB_ROOT is the directory where our HTML and other files reside.
       *  For this package, WEB_ROOT is the "webroot" directory under the working
       *  directory.
       *  The working directory is the location in the file system
       *  from where the java command was invoked.
       */
      // 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);
    
            // check if this is a request for a servlet or a static resource
            // a request for a servlet begins with "/servlet/"
            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);
          }
        }
      }
    }
    

      

    从代码可以看出,该类主要内容与上篇的HttpServer类似,不同点有:

    • await会一直等待HTTP请求,如果等到请求,该方法会根据请求类型分发给对应的处理器来处理;
    • 支持静态资源的请求,可以通过类似http://localhost:8080/index.html这样的请求来访问
    • index.html页面;
    • 支持servlet的请求和解析,可以通过类似http://localhost:8080/PrimitiveServlet来访问PrimitiveServlet

     

    Request与上篇介绍的Request无异,不再介绍,但是需要说明一点,这里的Request实现了ServletRequest接口。

    package day0522;
    
    import java.io.InputStream;
    import java.io.IOException;
    import java.io.BufferedReader;
    import java.io.UnsupportedEncodingException;
    import java.util.Enumeration;
    import java.util.Locale;
    import java.util.Map;
    import javax.servlet.RequestDispatcher;
    import javax.servlet.ServletInputStream;
    import javax.servlet.ServletRequest;
    
    
    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());
      }
    
      /* implementation of the ServletRequest*/
      public Object getAttribute(String attribute) {
        return null;
      }
    
      public Enumeration getAttributeNames() {
        return null;
      }
    
      public String getRealPath(String path) {
        return null;
      }
    
      public RequestDispatcher getRequestDispatcher(String path) {
        return null;
      }
    
      public boolean isSecure() {
        return false;
      }
    
      public String getCharacterEncoding() {
        return null;
      }
    
      public int getContentLength() {
        return 0;
      }
    
      public String getContentType() {
        return null;
      }
    
      public ServletInputStream getInputStream() throws IOException {
        return null;
      }
    
      public Locale getLocale() {
        return null;
      }
    
      public Enumeration getLocales() {
        return null;
      }
    
      public String getParameter(String name) {
        return null;
      }
    
      public Map getParameterMap() {
        return null;
      }
    
      public Enumeration getParameterNames() {
        return null;
      }
    
      public String[] getParameterValues(String parameter) {
        return null;
      }
    
      public String getProtocol() {
        return null;
      }
    
      public BufferedReader getReader() throws IOException {
        return null;
      }
    
      public String getRemoteAddr() {
        return null;
      }
    
      public String getRemoteHost() {
        return null;
      }
    
      public String getScheme() {
       return null;
      }
    
      public String getServerName() {
        return null;
      }
    
      public int getServerPort() {
        return 0;
      }
    
      public void removeAttribute(String attribute) {
      }
    
      public void setAttribute(String key, Object value) {
      }
    
      public void setCharacterEncoding(String encoding)
        throws UnsupportedEncodingException {
      }
    
    @Override
    public int getRemotePort() {
    	// TODO Auto-generated method stub
    	return 0;
    }
    
    @Override
    public String getLocalName() {
    	// TODO Auto-generated method stub
    	return null;
    }
    
    @Override
    public String getLocalAddr() {
    	// TODO Auto-generated method stub
    	return null;
    }
    
    @Override
    public int getLocalPort() {
    	// TODO Auto-generated method stub
    	return 0;
    }
    
    }
    

      

    Response

      同理,这里的Response也不在赘述。

    package day0522;
    
    import java.io.OutputStream;
    import java.io.IOException;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.File;
    import java.io.PrintWriter;
    import java.util.Locale;
    import javax.servlet.ServletResponse;
    import javax.servlet.ServletOutputStream;
    
    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
    " +
            "Content-Type: text/html
    " +
            "Content-Length: 23
    " +
            "
    " +
            "<h1>File Not Found</h1>";
          output.write(errorMessage.getBytes());
        }
        finally {
          if (fis!=null)
            fis.close();
        }
      }
    
    
      /** implementation of ServletResponse  */
      public void flushBuffer() throws IOException {
      }
    
      public int getBufferSize() {
        return 0;
      }
    
      public String getCharacterEncoding() {
        return null;
      }
    
      public Locale getLocale() {
        return null;
      }
    
      public ServletOutputStream getOutputStream() throws IOException {
        return null;
      }
    
      public PrintWriter getWriter() throws IOException {
        // autoflush is true, println() will flush,
        // but print() will not.
        writer = new PrintWriter(output, true);
        return writer;
      }
    
      public boolean isCommitted() {
        return false;
      }
    
      public void reset() {
      }
    
      public void resetBuffer() {
      }
    
      public void setBufferSize(int size) {
      }
    
      public void setContentLength(int length) {
      }
    
      public void setContentType(String type) {
      }
    
      public void setLocale(Locale locale) {
      }
    
    @Override
    public String getContentType() {
    	// TODO Auto-generated method stub
    	return null;
    }
    
    @Override
    public void setCharacterEncoding(String charset) {
    	// TODO Auto-generated method stub
    	
    }
    }
    

      

      这里的getWriter方法中新建了PrintWriter,其中第二个参数是一个boolean类型,表示是否启动autoFlush。

     

    StaticResourceProcessor

    package day0522;
    
    import java.io.IOException;
    
    public class StaticResourceProcessor {
    
      public void process(Request request, Response response) {
        try {
          response.sendStaticResource();
        }
        catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
    

      

    看代码可以看出:

      该类相较上篇是新建的类,主要实现的方法有sendStaticResource,实际上这个方法在上篇中也有,只是直接放在Response中出现,并在HttpServer中声明调用,而这里是将两种请求类型分别封装成类。

     

    ServletProcessor

    package day0522;
    
    import java.net.URL;
    import java.net.URLClassLoader;
    import java.net.URLStreamHandler;
    import java.io.File;
    import java.io.IOException;
    import javax.servlet.Servlet;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    
    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 {
          // 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 {
          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());
        }
    
      }
    }
    

      

    从代码看出:

    • 该类只有一个方法process,接收Request和Response两个参数;
    • 通过uri.substring来获取请求的servlet名;
    • 通过新建一个类加载器来装载请求的servlet类,用的类加载器为java.net.URLClassLoader;
    • 有了类加载器后,通过loadClass方法载入serlvet类;
    • 创建一个载入类的实例,并调用其service方法。

    至此,我们明白了:

    • servlet容器会等待http请求;
    • request负责封装http请求信息;
    • response负责封装相应信息;
    • staticResourceProcessor负责静态资源请求处理;
    • servletProcessor负责servlet的请求处理;
    • 一个简易的servlet容器的运作原理。

    如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。




    友情赞助

    如果你觉得博主的文章对你那么一点小帮助,恰巧你又有想打赏博主的小冲动,那么事不宜迟,赶紧扫一扫,小额地赞助下,攒个奶粉钱,也是让博主有动力继续努力,写出更好的文章^^。

        1. 支付宝                          2. 微信

                          

  • 相关阅读:
    linux下LD_PRELOAD的用处
    三个通用的脚本,处理MySQL WorkBench导出表的JSON数据进SQLITE3
    ubuntu 18.04下,KMS_6.9.1服务器启动后,客户端连接一段时间因为libnice而crash的问题修复
    Daliy Algorithm(线段树&组合数学) -- day 53
    Daliy Algorithm(链表&搜索&剪枝) -- day 52
    Daliy Algorithm(二分&前缀和) -- day 51
    每日算法
    动态规划--01背包模型
    每日算法
    每日算法
  • 原文地址:https://www.cnblogs.com/bigdataZJ/p/TomcatSourceZJ3.html
Copyright © 2020-2023  润新知