How Tomcat Works - A Simple Web Server

1. Http协议

1.1 Http 请求

一个http请求包含了三个部分:

1)方法 - 统一资源定位符(uri) - 协议

2)请求头

3)请求实体

一个例子:

POST /examples/default.jsp HTTP/1.1
Accept: text/plain; text/html Accept-Language: en-gb Connection: Keep-Alive
Host: localhost
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98) Content-Length: 33
Content-Type: application/x-www-form-urlencoded Accept-Encoding: gzip, deflate

lastName=Franks&firstName=Michael

这里的方法是POST,其他支持的方法还有GET,OPTIONS,HEAD,PUT,DELETE。URI是 /examples/default.jsp表示请求资源的路径。协议是HTTP/1.1

第二部分就是请求头,各项之间用回车/换行(CRLF)分割。

最后一部分是请求实体,这里就是

lastName=Franks&firstName=Michael

请求实体和请求头之间是用一个只包含CRLF的空行分割。

1.2 Http响应

和http请求类似,http响应也包含了三部分:

1)协议-状态码-描述

2)响应头

3)响应实体

一个例子:

HTTP/1.1 200 OK
Server: Microsoft-IIS/4.0
Date: Mon, 5 Jan 2004 13:13:33 GMT Content-Type: text/html
Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT     Content-Length: 112

<html>
<head>
<title>HTTP Response Example</title> </head>
<body>
Welcome to Brainy Software
</body>
</html>

协议是http/1.1,状态码200,描述是OK。响应头各项之间也是用CRLF分割。响应头和响应实体之间也是一个包含CRLF的空行分割。

2. 一个简单的web服务器

这个例子实现了一个简单的web服务器,包含HttpServer,Response和Request三个java类和一些静态的资源文件。

HttpServer.java负责监听请求:

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;
      }
    }
  }
}

Request.java:

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;
  }

}

Response.java:

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

HttpServer 接受web请求然后将封装成一个request对象,request对请求的uri进行解析,在webroot里面找到对应的静态文件,然后写到socket的outputstream。如果文件不存在,则写回一个404错误信息。

测试会发现在各个浏览器上测试结果不一样。

IE上如果访问的页面没有找到,会直接显示404,不会显示我们响应的错误信息。如果是访问localhost:8080/index.html 可以正常显示。

Firefox如果访问的页面没有找到,会显示我们响应的404错误信息,如果访问localhost:8080/index.html会显示HTML文件的源码。

注意Response sendStaticResource 这个方法是有问题的,因为根据http协议,响应体之前还有方法,状态码,响应头等信息。但是这里是直接把文件内容给写回去了。为了能把这部分内容加上去,对HttpServer做了一些修改;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;

/*
  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
*/

class Response {
    Request request;
    OutputStream output;

    public Response(OutputStream output) {
        this.output = output;
    }

    public void setRequest(Request request) {
        this.request = request;
    }

    public void sendStaticResource() throws IOException {
        try {
            File file = new File(HttpServer.WEB_ROOT, request.getUri());
            if (file.exists()) {
                StringBuilder response = new StringBuilder();
                response.append("HTTP/1.1 200 OK\r\n");

                // Hack it
                if (request.getUri().startsWith("/images")) {
                    response.append("Content-Type: image/png\r\n");
                } else {
                    response.append("Content-Type: text/html\r\n");
                }
                response.append("Content-Length: ").append(file.length()).append("\r\n");
                response.append("\r\n");

                output.write(response.toString().getBytes());
                // Write file
                output.write(Files.readAllBytes(file.toPath()));
            } 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());
        }
    }
}

 现在在firfox和chrome上都可以正常工作。

 

posted @ 2017-08-04 10:44  liketic  阅读(136)  评论(0编辑  收藏  举报