• 探秘Tomcat——从一个简陋的Web服务器开始


    前言:

      无论是之前所在实习单位小到一个三五个人做的项目,还是如今一个在做的百人以上的产品,一直都能看到tomcat的身影。工作中经常遇到的操作就是启动和关闭tomcat服务,或者修改了摸个java文件,编译该文件,将生成的class文件塞到tomcat目录下相应的jar包中去,以使其生效,但是也可以热部署,不需要这么繁琐的操作。

      总之,一直以来都是习惯了tomcat的存在,没有深究tomcat的运行机制和原理,上一次对于tomcat源码的跃跃欲试还是去年的事儿了——《探秘Tomcat(一)——Myeclipse中导入Tomcat源码》。在这篇文章中,我下载了tomcat6版本的代码,并将其导入到eclipse中,时隔一年多,原来的项目还在,但是为显诚意,我还是重头做了一遍导入tomcat源码到eclipse的操作。

      对于tomcat的崇敬之情让我决定深入其中,一探究竟。于是我找到了网上大家首推的教材——《深入剖析tomcat》,书比较老,但是很权威,不影响原理性东西的理解。目前已经看到第二章。

     

      读过或者了解该书的应该都知道,这不是一本上来就直接告诉你tomcat的设计思想,用到的什么设计模式或者源码中某一行有什么匠心独运的地方。该书采用一个循序渐进的方式从一个简单的不能再简单的servlet容器开始,之后慢慢丰富,添加功能模块,最终形成我们想知道的tomcat的模样。

     

    背景知识:

    • HTTP请求:

        请求方法——统一资源标识符URI——协议/版本

        请求头

        实体

    比如这里我们可以看到请求的方法Request Method是GET, Request URL为http://tech.qq.com/a/20160604/007535.htm,并且分别有Request和下面要讲的Response的请求头信息,如Content-Type等。

     

    • HTTP共支持7中请求方法

        GET,POST,HEAD,OPTIONS,PUT,DELETE,TRACE

     

    • HTTP响应

        协议——状态码——描述

        响应头

        响应实体段

     

    • Socket

        socket在应用程序中用于从网络中读取数据,实现不同计算机之间的通讯,实现一个socket需要知道对应应用程序的ip地址和端口号。

        首先创建该socket类的实例,有了该实例后,就可以使用它实现发送或接收字节流。如果想要发送字节流,需要调用socket类的getOutputStream来获取一个java.io.OutputStream对象;要发送文本到远程应用程序,需要使用返回的OutputStream对象创建一个java.io.PrintWriter对象;要从连接的另一端接收字节流,需要调用Socket类的getInputStream方法,其会返回一个java.io.InputStream对象。

     

    • ServerSocket类

        有了客户端的socket可以发送请求,如果没有服务端来响应,发送的请求也是肉包子打狗,ServerSocket就是充当服务端的角色,serversocket出于随时待命的状态,一旦有客户端发出请求,serversocket就要给出响应,从而实现与客户端的通信。

     

    请求响应模型

      有了以上的背景知识,我们就可以实现一个简单到爆的通讯模型,新建一个socket客户端通讯类,用于发送和接收数据,还需要创建一个服务端的ServerSocket用于监听和响应客户端的请求。

      主要包含以下三个类:

      HttpServer:模拟一个Web服务器

    package myTest;
    
    import java.net.Socket;
    import java.net.ServerSocket;
    import java.net.InetAddress;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.IOException;
    import java.io.File;
    
    public class HttpServer {
    
      /** 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.
       */
      public static final String WEB_ROOT =
        System.getProperty("user.dir") + File.separator  + "webroot";
    
      // shutdown command
      private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
    
      // the shutdown command received
      private boolean shutdown = false;
    
      public static void main(String[] args) {
        HttpServer server = new HttpServer();
        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);
            response.sendStaticResource();
    
            // 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();
            continue;
          }
        }
      }
    }
    

      

    从代码可以看出:

    1. 类中分别创建了socket和serversocket;
    2. 定义了一个WEB_ROOT目录,其中存放了对应请求的相应结果文件;
    3. 定义了一个关闭命令,通过在浏览器中输入类似.../SHUTDOWN来关闭Web服务器
    4. 创建了一个await方法,一直监听127.0.0.1的8080端口,如果有请求产生(比如在浏览器中输入一个请求地址),则会进入await方法并执行serverSocket的accept方法。

     

      Request类:

      模拟一个HTTP请求。

    package myTest;
    
    import java.io.InputStream;
    import java.io.IOException;
    
    public class Request {
    
      private InputStream input;
      private String uri;
    
      public Request(InputStream input) {
        this.input = input;
      }
    
      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());
      }
    
      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 String getUri() {
        return uri;
      }
    
    }
    

      

    从代码中可以发现:

    1. 可以实现传递InputStream对象,在处理与客户端通讯的Socket对象中获取;
    2. 调用InputStream对象的read来获取HTTP请求的原始数据;
    3. parse方法用于解析HTTP请求中的原始数据(原始数据由上面的getInputStream中获得);
    4. parseUri作为一个私有方法被parse调用,用于解析HTTP请求的URI

     

      Response类:

      模拟HTTP的响应。

    package myTest;
    
    import java.io.OutputStream;
    import java.io.IOException;
    import java.io.FileInputStream;
    import java.io.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
    */
    
    public class Response {
    
      private static final int BUFFER_SIZE = 1024;
      Request request;
      OutputStream output;
    
      public Response(OutputStream output) {
        this.output = output;
      }
    
      public void setRequest(Request request) {
        this.request = request;
      }
    
      public void sendStaticResource() throws IOException {
        byte[] bytes = new byte[BUFFER_SIZE];
        FileInputStream fis = null;
        try {
          File file = new File(HttpServer.WEB_ROOT, request.getUri());
          if (file.exists()) {
            fis = new FileInputStream(file);
            int ch = fis.read(bytes, 0, BUFFER_SIZE);
            while (ch!=-1) {
              output.write(bytes, 0, ch);
              ch = fis.read(bytes, 0, BUFFER_SIZE);
            }
          }
          else {
            // file not found
            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());
          }
        }
        catch (Exception e) {
          // thrown if cannot instantiate a File object
          System.out.println(e.toString() );
        }
        finally {
          if (fis!=null)
            fis.close();
        }
      }
    }
    

      

    从代码可以发现:

    1. Request类似,这里通过接受OutputStream构造了Response对象;
    2. setRequest方法用于接收Request对象,因为在Response中需要用到Request的getUri方法;
    3. sendStaticResource方法主要用于处理请求的响应,如这里发送一个静态资源html作为请求的结果

    至此, 本篇主要提到:

    • 一些基本概念如http请求、http响应、socket等;
    • 对于一个超级简陋的web服务器有了基本的认识;
    • 明确了客户端和服务端各自的角色和职责。

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

      


    友情赞助

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

        1. 支付宝                          2. 微信

                          

  • 相关阅读:
    码到成功——Beta冲刺随笔 day 6
    团队作业第六次——Beta冲刺
    Beta冲刺 —— 6.2
    用户调查报告
    Beta冲刺 —— 总结随笔
    Beta冲刺——测试随笔
    Beta冲刺 —— 6.1
    Beta冲刺 —— 5.31
    Beta冲刺 —— 5.30
    Beta冲刺 —— 5.29
  • 原文地址:https://www.cnblogs.com/bigdataZJ/p/TomcatSourceZJ2.html
Copyright © 2020-2023  润新知