前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >探秘Tomcat——从一个简陋的Web服务器开始

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

作者头像
JackieZheng
发布2018-01-16 17:15:12
6910
发布2018-01-16 17:15:12
举报
文章被收录于专栏:JackieZhengJackieZhengJackieZheng

前言:

  无论是之前所在实习单位小到一个三五个人做的项目,还是如今一个在做的百人以上的产品,一直都能看到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\r\n" +
          "Content-Type: text/html\r\n" +
          "Content-Length: 23\r\n" +
          "\r\n" +
          "<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的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-06-04 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档