• 理解tomcat之搭建简易http服务器


       做过java web的同学都对tomcat非常熟悉。我们在使用tomcat带来的便利的同时,是否想过tomcat是如何工作的呢?tomcat本质是一个http服务器,本篇文章将搭建一个简单的http服务器。

    1 Catalina模型

       首先我们先了解一下tomcat的大致工作原理。tomcat的核心是servlet容器,我们称它为Catalina(为什么叫这个名字?我也不知道 ̄へ ̄)。模型图如1.1

         

                                            图1.1

        Connector是用来“连接”容器里边的请求的。它的工作是为接收到每一个 HTTP 请求构造一个 request 和 response 对象。然后它把流程传递给容器。容器从连接器接收到 requset 和 response 对象之后调用 servlet 的 service 方法用于响应。谨记,这个描述仅仅是冰山一角而已。这里容器做了相当多事情。例如,在它调用 servlet 的 service 方法之前,它必须加载这个 servlet,验证用户(假如需要的话),更新用户会话等等。以此为思路,我们就开始我们的构造http服务器之旅吧。

    2 服务器搭建

      首先我们明确一下我们的服务器的功能点。

      1. 需要有一个类去接收http请求;

      2. 需要一个自定义Request类和Response类,把接收到的请求构造成这两个类;

      3. 根据请求的格式来确定处理方式:返回静态资源 or 进入Servlet ?

      4. 需要一个Servlet类执行业务逻辑

      UML图如下2.1

        

                                         图2.1

    2.1 HttpServer 

    首先构造HttpServer类

     1 public class HttpServer {
     2 
     3 
     4     private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
     5     private static boolean shutdown = false;
     6 
     7     public static void main(String[] args) {
     8         HttpServer server = new HttpServer();
     9         server.await();
    10     }
    11 
    12     public static void await() {
    13         ServerSocket serverSocket = null;
    14         int port = 8080;
    15         try {
    16             serverSocket = new ServerSocket(port, 1,
    17                     InetAddress.getByName("127.0.0.1"));
    18         } catch (IOException e) {
    19             e.printStackTrace();
    20             System.exit(1);
    21         }
    22         // Loop waiting for a request
    23         while (!shutdown) {
    24             Socket socket = null;
    25             InputStream input = null;
    26             OutputStream output = null;
    27             try {
    28                 socket = serverSocket.accept();
    29                 input = socket.getInputStream();
    30                 output = socket.getOutputStream();
    31                 // create Request object and parse
    32                 Request request = new Request(input);
    33                 request.parseUrl();
    34                 // create Response object
    35                 Response response = new Response(output);
    36                 response.setRequest(request);
    37 
    38                 if (request.getUri().startsWith("/v2/")) {
    39                     ServletProcessor processor = new ServletProcessor();
    40                     processor.process(request, response);
    41                 }
    42                 else {
    43                     StaticResourceProcessor processor =
    44                             new StaticResourceProcessor();
    45                     processor.process(request, response);
    46                 }
    47                 // Close the socket
    48                 socket.close();
    49                 //check if the previous URI is a shutdown command
    50                 shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
    51             } catch (Exception e) {
    52                 e.printStackTrace();
    53                 System.exit(1);
    54             }
    55         }
    56     }
    57 }
    View Code

      我们的服务器启动入口放在了HttpServer里面。await()方法负责接收Socket连接,只有当用户输入了代表shutdown的URL时,服务器才会停止运行。Request类提供了解析请求的功能,根据请求的url,来决定是返回静态资源,或者进入对应的servlet类执行service逻辑。

        这里我们需要关注一下ServerSocket这个类的用法。Socket 类代表一个客户端套接字,即任何时候你想连接到一个远程服务器应用的时候,你都会第一时间想到这个类。而ServerSocket 和 Socket 不同,服务器套接字的角色是等待来自客户端的连接请求。一旦服 务器套接字获得一个连接请求,它创建一个 Socket 实例来与客户端进行通信。 ServletSocket套接字的其中一个构造函数为

    public ServerSocket(int port, int backLog, InetAddress bindingAddress); 

        port代表端口号,backLog代表这个套接字可支持的最大连接数量,bindingAddress代表服务器绑定的地址。一旦你有一个 ServerSocket 实例,你可以通过调用 ServerSocket 类的 accept 方法j。这个监听当前地址的当前端口上的请求,方法只会在有连接请求时才会返回,并且返回值是一个 Socket 类的实例。

    2.2 Request Response

         servlet 的 service 方法从 servlet 容器中接收一个 javax.servlet.ServletRequest 实例 和一个 javax.servlet.ServletResponse 实例。这就是说对于每一个 HTTP 请求,servlet 容器 必须构造一个 ServletRequest 对象和一个 ServletResponse 对象并把它们传递给正在服务的 servlet 的 service 方法。 

      1 public class Request implements ServletRequest {
      2 
      3     private InputStream input;
      4     private String uri;
      5 
      6     public Request(InputStream input) {
      7         this.input = input;
      8     }
      9 
     10     public String getUri(){
     11         return uri;
     12     }
     13 
     14     public void parseUrl() {
     15         StringBuffer request = new StringBuffer(2048);
     16         int i;
     17         byte[] buffer = new byte[2048];
     18 
     19         try {
     20             i = input.read(buffer);
     21         } catch (IOException e) {
     22             e.printStackTrace();
     23             i = -1;
     24         }
     25 
     26         for (int j = 0; j < i; j++) {
     27             request.append((char) buffer[j]);
     28         }
     29 
     30         System.out.print(request.toString());
     31         uri = parseUri(request.toString());
     32     }
     33 
     34     private static String parseUri(String requestString) {
     35         int index1, index2;
     36         index1 = requestString.indexOf(' ');
     37         if (index1 != -1) {
     38             index2 = requestString.indexOf(' ', index1 + 1);
     39             if (index2 > index1)
     40                 return requestString.substring(index1 + 1, index2);
     41         }
     42         return null;
     43     }
     44 
     45     @Override
     46     public Object getAttribute(String name) {
     47         return null;
     48     }
     49 
     50     @Override
     51     public Enumeration getAttributeNames() {
     52         return null;
     53     }
     54 
     55     @Override
     56     public String getCharacterEncoding() {
     57         return null;
     58     }
     59 
     60     @Override
     61     public void setCharacterEncoding(String env) throws UnsupportedEncodingException {
     62 
     63     }
     64 
     65     @Override
     66     public int getContentLength() {
     67         return 0;
     68     }
     69 
     70     @Override
     71     public String getContentType() {
     72         return null;
     73     }
     74 
     75     @Override
     76     public ServletInputStream getInputStream() throws IOException {
     77         return null;
     78     }
     79 
     80     @Override
     81     public String getParameter(String name) {
     82         return null;
     83     }
     84 
     85     @Override
     86     public Enumeration getParameterNames() {
     87         return null;
     88     }
     89 
     90     @Override
     91     public String[] getParameterValues(String name) {
     92         return new String[0];
     93     }
     94 
     95     @Override
     96     public Map getParameterMap() {
     97         return null;
     98     }
     99 
    100     @Override
    101     public String getProtocol() {
    102         return null;
    103     }
    104 
    105     @Override
    106     public String getScheme() {
    107         return null;
    108     }
    109 
    110     @Override
    111     public String getServerName() {
    112         return null;
    113     }
    114 
    115     @Override
    116     public int getServerPort() {
    117         return 0;
    118     }
    119 
    120     @Override
    121     public BufferedReader getReader() throws IOException {
    122         return null;
    123     }
    124 
    125     @Override
    126     public String getRemoteAddr() {
    127         return null;
    128     }
    129 
    130     @Override
    131     public String getRemoteHost() {
    132         return null;
    133     }
    134 
    135     @Override
    136     public void setAttribute(String name, Object o) {
    137 
    138     }
    139 
    140     @Override
    141     public void removeAttribute(String name) {
    142 
    143     }
    144 
    145     @Override
    146     public Locale getLocale() {
    147         return null;
    148     }
    149 
    150     @Override
    151     public Enumeration getLocales() {
    152         return null;
    153     }
    154 
    155     @Override
    156     public boolean isSecure() {
    157         return false;
    158     }
    159 
    160     @Override
    161     public RequestDispatcher getRequestDispatcher(String path) {
    162         return null;
    163     }
    164 
    165     @Override
    166     public String getRealPath(String path) {
    167         return null;
    168     }
    169 
    170     @Override
    171     public int getRemotePort() {
    172         return 0;
    173     }
    174 
    175     @Override
    176     public String getLocalName() {
    177         return null;
    178     }
    179 
    180     @Override
    181     public String getLocalAddr() {
    182         return null;
    183     }
    184 
    185     @Override
    186     public int getLocalPort() {
    187         return 0;
    188     }
    189 }
    View Code

         Request类代表一个 request 对象并被传递给 servlet 的 service 方法。就本身而言,它必须实现 javax.servlet.ServletRequest 接口。这个类必须提供这个接口所有方法的实现。不过,我们想要让它非常简单并且仅仅提供实现其中一些方法,比如解析url的功能。在Request初始化时初始化成员变量inputStream,并且用parseUrl()方法创建了一个字节数组来读入输入流,并转化为成一个StringBuffer对象,进而解析url。

      1 public class Response implements ServletResponse {
      2 
      3     private static final int BUFFER_SIZE = 1024;
      4     Request request;
      5     OutputStream output;
      6     PrintWriter writer;
      7 
      8     public Response(OutputStream output) {
      9         this.output = output;
     10     }
     11 
     12     public void setRequest(Request request) {
     13         this.request = request;
     14     }
     15 
     16     public void sendStaticResource() throws IOException {
     17         byte[] bytes = new byte[BUFFER_SIZE];
     18         FileInputStream fis = null;
     19         try {
     20             File file = new File(HttpServer.WEB_ROOT, request.getUri());
     21             if (file.exists()) {
     22                 fis = new FileInputStream(file);
     23                 int ch = fis.read(bytes, 0, BUFFER_SIZE);
     24                 while (ch != -1) {
     25                     output.write(bytes, 0, ch);
     26                     ch = fis.read(bytes, 0, BUFFER_SIZE);
     27                 }
     28             } else {
     29                 String errorMessage = "HTTP/1.1 404 File Not Found
    " +
     30                         "Content-Type: text/html
    " + "Content-Length: 23
    " + "
    " +
     31                         "<h1>File Not Found</h1>";
     32                 output.write(errorMessage.getBytes());
     33             }
     34         } catch (Exception e) {
     35             System.out.println(e.toString());
     36         } finally {
     37             if (fis != null)
     38                 fis.close();
     39         }
     40     }
     41 
     42     @Override
     43     public String getCharacterEncoding() {
     44         return null;
     45     }
     46 
     47     @Override
     48     public String getContentType() {
     49         return null;
     50     }
     51 
     52     @Override
     53     public ServletOutputStream getOutputStream() throws IOException {
     54         return null;
     55     }
     56 
     57     @Override
     58     public PrintWriter getWriter() throws IOException {
     59         writer = new PrintWriter(output, true);
     60         return writer;
     61 
     62     }
     63 
     64     @Override
     65     public void setCharacterEncoding(String charset) {
     66 
     67     }
     68 
     69     @Override
     70     public void setContentLength(int len) {
     71 
     72     }
     73 
     74     @Override
     75     public void setContentType(String type) {
     76 
     77     }
     78 
     79     @Override
     80     public void setBufferSize(int size) {
     81 
     82     }
     83 
     84     @Override
     85     public int getBufferSize() {
     86         return 0;
     87     }
     88 
     89     @Override
     90     public void flushBuffer() throws IOException {
     91 
     92     }
     93 
     94     @Override
     95     public void resetBuffer() {
     96 
     97     }
     98 
     99     @Override
    100     public boolean isCommitted() {
    101         return false;
    102     }
    103 
    104     @Override
    105     public void reset() {
    106 
    107     }
    108 
    109     @Override
    110     public void setLocale(Locale loc) {
    111 
    112     }
    113 
    114     @Override
    115     public Locale getLocale() {
    116         return null;
    117     }
    118 }
    View Code

         Response类则提供了发送静态资源的功能。sendStaticResource()方法根据request内解析过的url,在本地寻找指定文件。如果找得到,把文件内容读出到浏览器,如果找不到,那么返回404的错误码。

    2.3 PrimitiveServlet类    

     1 public class PrimitiveServlet implements Servlet {
     2 
     3     @Override
     4     public void init(ServletConfig config) throws ServletException {
     5         System.out.println("init");
     6     }
     7 
     8     @Override
     9     public ServletConfig getServletConfig() {
    10         return null;
    11     }
    12 
    13     @Override
    14     public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    15         System.out.println("from service");
    16         PrintWriter out = res.getWriter();
    17         out.println("Hello. Roses are red.");
    18         out.print("Violets are blue.");
    19     }
    20 
    21     @Override
    22     public String getServletInfo() {
    23         return "this is v2 info";
    24     }
    25 
    26     @Override
    27     public void destroy() {
    28         System.out.println("destroy");
    29     }
    30 }
    View Code

        PrimitiveServlet实现了标准的Servlet接口。我们简单的实现了Servlet生命周期的其他方法,并在service()方法我们做了最简单的向浏览器吐文字的操作。

    2.4 ServletProcessor 和 StaticResourceProcessor

     1 public class ServletProcessor {
     2 
     3     public void process(Request request, Response response) {
     4         String uri = request.getUri();
     5         String servletName = uri.substring(uri.lastIndexOf("/") + 1);
     6         Class myClass = null;
     7         try {
     8             myClass = Class.forName("tomcat.v2." + servletName);
     9         } catch (ClassNotFoundException e) {
    10             System.out.println(e.toString());
    11         }
    12         Servlet servlet = null;
    13         try {
    14             servlet = (Servlet) myClass.newInstance();
    15             servlet.service((ServletRequest) request, (ServletResponse) response);
    16         } catch (Exception e) {
    17             e.printStackTrace();
    18         } catch (Throwable e) {
    19             e.printStackTrace();
    20         }
    21     }
    22 }
    View Code

        ServletProcessor是处理serlvet请求的类。它的作用在于,根据请求的路径实例化对应的Servlet,并且执行该Servlet的service()方法。

     1 public class StaticResourceProcessor {
     2 
     3     public void process(Request request, Response response){
     4         try{
     5             response.sendStaticResource();
     6         }catch (IOException e){
     7             e.printStackTrace();
     8         }
     9     }
    10 }
    View Code

      StaticResourceProcessor则简单的调用response的sendStaticResource()方法来返回静态资源。

        最后我们还需要建一个辅助类Constants指定静态资源的存放路径

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

     3 启动服务器

        启动main函数来启动我们的http服务器。在浏览器输入http://localhost:8080/test,得到的结果如图3.1

           

                                                   图3.1

    这个url访问的是我的电脑中的一个文件test,它的存储路径为 /Users/wangyu/Documents/workspace/Tomcat/webroot/test,即当前项目的webroot目录下。

    我们还可以输入一个servlet地址查看效果。在浏览器输入http://localhost:8080/v2/PrimitiveServlet,得到的结果如图3.2

            

                               图3.2  

         这和我们的PrimitiveServlet的输出是一致的。

    4 结语

        当然,tomcat的功能比我们的简易http服务器强大的多。但是通过这个最简单的http服务器,是否让你对http服务器有更深的一点了解了呢?

    参考资料:

    1 深入剖析Tomcat

    作者:mayday芋头
    本博客中未标明转载的文章归作者mayday芋头和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    微服务概述
    Airflow 配置celery+rabbitmq和celery+redis
    CentOS7安装Airflow
    Python如何import其它.py文件及其函数
    ERROR 1819 (HY000): Your password does not satisfy the current policy requirements
    CentOS7安装MySQL
    Hadoop完全分布式环境下,DataNode进程正常启动,但是网页上不显示DataNode节点
    <一> windbg简介
    几个资料下载网站
    使用VS2012 C++ 进行单元测试
  • 原文地址:https://www.cnblogs.com/maypattis/p/5549613.html
Copyright © 2020-2023  润新知