Purely handwritten Tomcat, if you don’t understand it, come and beat me [with source code, detailed explanations with pictures and text]

The source code is placed at the end of the article

theoretical knowledge

What is Tomcat

        Tomcat is an open source Servlet container that implements Java EE specifications such as Java Servlet, JavaServer Pages (JSP), and WebSocket and is used to run Java Web applications on Web servers.

        To put it simply, Tomcat can handle requests transmitted from the network.

Input and output streams

        In other words, Tomcat needs to help us complete the connection and transmission between the client and the server. During transmission, input and output streams are used for transmission.

In the final analysis, the communication between the client and the server is the transmission of two data. The client sends inputStream to the server, and the server replies outputStream to the client.

HTTP request

 

The http request is the data transmission protocol sent by the web browser to the web server (Tomcat). That is to say, negotiate a format for transmission, so that after the server receives it, it can parse it, understand what the browser wants to express, and then provide feedback.

http request protocol part data

GET /user HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

Part 1: Request line: request type, resource path, and http version (first line above)

Part 2: Request header: Immediately after the request line, used to describe additional information that the server needs to use (lines 2 to 8)

Part 3: Blank line (there must be a line break between the request header and the body)

Part 4: Main data, you can add any data

HTTP response

        An HTTP response is the data returned by the web server to the client (usually a browser). When the client sends an HTTP request, the server generates an HTTP response based on the content and requirements of the request and sends it back to the client. When imitating Tomcat, it is important to understand the structure and content of the HTTP response.

        An HTTP response is data sent by the server to the client in response to the client's request. HTTP responses need to meet certain formats and requirements. The following is the format of a standard HTTP response.

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 37

<html>
<head>
    <title>简单的HTTP响应示例</title>
</head>
<body>
    <h1>Hello, World!</h1>
</body>
</html>
  • HTTP/1.1 200 OK: Status line, indicating that the HTTP protocol version is 1.1, the status code is 200, and the status phrase is OK. This indicates that the request was successful.

  • Content-Type: text/html: Response header, specifying the response content type as HTML.

  • Content-Length: 37: Response header, specifying the length of the response content, in bytes.

  • Blank line: used to separate response headers and response bodies.

  • Response body: actual response content. In this case, it's a simple HTML page that displays "Hello, World!".

Static requests and dynamic requests

        In web services, static requests and dynamic requests are two different types of HTTP requests used to obtain and render web page content. They have different features and uses:

  1. Static request: Static request means that the browser requests static resources on the server, such as HTML, CSS, JavaScript, image files, etc. These resources are stored as unchangeable files on the server.
  2. Dynamic request: Dynamic request refers to the browser requesting dynamically generated content on the server, which is usually generated through server-side program logic, such as PHP, Python, Ruby and other scripting languages. The server generates content based on the requested parameters and logic, and then returns the content to the browser.

        In short, both static requests and dynamic requests play an important role in Web services. Static requests are used to provide fixed resources, while dynamic requests are used to generate personalized, real-time updated content.

Project demonstration

Start Tomcat

 Visit homepage

URL input error, 404 page test

 

Static request test: access html page 

Static request test: css

 Dynamic request test: login

 

 

Project Flow

Project directory

 

simple flow chart

The client (browser) sends a request, and Tomcat has been listening. When it receives the request, it starts to parse the request information. After the parsing is completed, it starts processing the request. After the processing is completed, it encapsulates the response information and returns it to the front end. 

Detailed flow chart

 Please see the detailed process explained below.

1 Startup class: MyTomcat

  1. serverSocket: is a ServerSocket object, which is created through the ServerSocket class and listens for connection requests on a specific port.
  2. The accept() method is a method of the ServerSocket class. It blocks program execution and waits for the client to connect. When a client connects to the server, the accept() method will return a new Socket object representing the connection with the client.

2. Thread task processing class: ThreadTask

Specifically, you need to do the following four steps:

  1. Parse and encapsulate the stream data received by the client to obtain the request object
  2. Get the response object based on the stream data and request object
  3. Handle static requests and dynamic requests separately to improve response objects
  4. close connection

These four steps are all of tomcat, but there are many specific subdivisions of each step.

 

 

3. Request class: HttpServletRequest

The request information class parses and encapsulates the input stream data received by the client to obtain the request object.

Convert the input stream into String and start parsing

/**
 * 将输入流转换成String,开始解析
 */
public HttpServletRequest(InputStream iis) {
       //一次性读完所有请求信息
   StringBuilder sb = new StringBuilder();
   int length = -1;
   byte[] bs = new byte[100*1024];
   try {
      length = iis.read(bs);//读取socket输入流数据,将其放到byte数组里面
   } catch (IOException e) {
      e.printStackTrace();
      System.out.println("读取客户请求异常");
       }
   //将bs中的字节数据转为char
   for(int i = 0;i<length;i++){
      sb.append((char)bs[i]);
   }
   content = sb.toString();//将sb转换成String,存到content里面
   parseProtocol();      //开始解析
}

Specific parsing operations

parse protocol

/**
 * 解析协议
 */
private void parseProtocol() {
   String[] ss = content.split(" ");
   //解析 请求方法类型,存到method
   this.method = ss[0];

   //解析 请求地址,存到requestURI
   this.requestURI = ss[1];

   //解析 请求参数,存到parameter的map中
   parseParameter();

   //解析 请求头,存到headers中
   parseHeader();

   //解析 请求cookie:从headers中取cookie
   parseCookie();

   //解析 sessionId:从cookie中取出jsessionid
   jsessionid = parseJSessionId();
}

Various analysis methods

private String parseJSessionId() {
   if(cookies!=null&&cookies.size()>0) {
      for(Cookie c:cookies) {
         if("JSESSIONID".equals(c.getName())) {
            return c.getValue();
         }
      }
   } 
   return null;
}

   /**
    * headers中取出cookie,然后在解析出cookie对象存在cookies中
 * 取出协议中的 Cookie:xxxx  ,如果有则说明已经生成过Cookie  没有则表明是第一次请求,要生成Cookie编号
    */
private void parseCookie() {
   if(headers==null&&headers.size()<=0){
      return;
   }
   //从headers中取出键为cookie的 
   String cookieValue = headers.get("Cookie");
   if(cookieValue == null || cookieValue.length()<=0) {
      return;
   }
   String[] cvs = cookieValue.split(": ");
   if(cvs.length > 0) {
      for(String cv:cvs) {
         String[] str = cv.split("=");
         if(str.length > 0) {
            String key = str[0];
            String value = str[1];
            Cookie c = new Cookie(key,value);
            cookies.add(c);
         }
      }
   }
}
private void parseHeader() {
       //请求头
   String[] parts = this.content.split("\r\n\r\n");
       //GET /请求地址 HTTP/1.1
   String[] headerss = parts[0].split("\r\n");
      for(int i = 1;i<headerss.length;i++){
         String[] headPair = headerss[i].split(": ");
               //Host: localhost:8888     Connection: keep-alive ...
         headers.put(headPair[0], headPair[1]);
      }
}

   /**
    * 取参数
    */
private void parseParameter() {
   //requestURI: user.action?name=z&password=a
   int index = this.requestURI.indexOf("?");
   //有?的话
   if(index>=1){
      String[] pairs = this.requestURI.substring(index+1).split("&");
      for(String p:pairs){
         String[] po = p.split("=");
         parameter.put(po[0], po[1]);
      }
   }
   if(this.method.equals("POST")){
      String[] parts = this.content.split("\r\n\r\n");
      String entity = parts[1];
      String[] pairs = entity.split("&");
      for(String p:pairs){
         String[] po = p.split("=");
         parameter.put(po[0], po[1]);
      }
   }
}

4. Response class: HttpServletResponse

        Response class: Get the URL according to the passed request object, find the requested resource file, set the corresponding response type, write the file into the response stream and return

        If it is a static request, the corresponding class will be called, because the static request is to obtain a certain HTML, CSS, JavaScript, image, etc. file, so we only need to parse the name of the file from the request URL, then find the file, and then Just write it into the output stream according to the format of the http response and return it to the front end.

Call the send method according to different types

//3、发送文件响应,不同的文件返回不同类型
      if(file.getName().endsWith(".jpg")){
          send(file,"application/x-jpg",code);
      }else if(file.getName().endsWith(".jpe")||file.getName().endsWith(".jpeg")){
          send(file,"image/jpeg",code);
      }else if(file.getName().endsWith(".gif")){
          send(file,"image/gif",code);
      }else if(file.getName().endsWith(".css")){
          send(file,"text/css",code);
      }else if(file.getName().endsWith(".js")){
          send(file,"application/x-javascript",code);
      }else if(file.getName().endsWith(".swf")){
          send(file,"application/x-shockwave-flash",code);
      }else{
          send(file,"text/html",code);
      }

The send method first calls the genProtocol method and splices the response format first.

   /**
    * 拼接响应协议
    */
private String genProtocol(long length, String contentType, int code) {
   String result = "HTTP/1.1 "+code+" OK\r\n";
   result+="Server: myTomcat\r\n";
   result+="Content-Type: "+contentType+";charset=utf-8\r\n"; 
   result+="Content-Length: "+length+"\r\n";
   result+="Date: "+new Date()+"\r\n"; 
   result+="\r\n";
   return result;
}

The send method then calls the readFile method to read the file and return a byte array.

   /**
    * 读取文件
    */
private byte[] readFile(File file) {
   ByteArrayOutputStream baos = new ByteArrayOutputStream();
   FileInputStream fis = null;
   
   try {
      fis = new FileInputStream(file);
      byte[] bs = new byte[1024];
      int length;
      while((length = fis.read(bs,0,bs.length))!=-1){
         baos.write(bs, 0, length);
         baos.flush();
      }
   } catch (Exception e) {
      e.printStackTrace();
   }finally{
      try {
               fis.close();
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
   return baos.toByteArray();
}

Finally, the send method returns the front-end streaming data.

   /**
    * 返回给前端响应流
    */
private void send(File file, String contentType, int code) {
   try {
      String responseHeader = genProtocol(file.length(),contentType,code);
      byte[] bs = readFile(file);
      
      this.oos.write(responseHeader.getBytes());
      this.oos.flush();//往前端传过去
      this.oos.write(bs);
      this.oos.flush();
   } catch (IOException e) {
      e.printStackTrace();
   }finally{
      try {
         this.oos.close();
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}

5. Processing interfaces, dynamic processing classes, and static processing classes

        Static processing means that the front end needs some static resources, such as HTML, CSS, pictures, etc. The back end can directly find the file and return it to the front end according to the format.

        Dynamic processing has corresponding business, and the corresponding servlet needs to be called.

        To distinguish between dynamic and static, here is how I distinguish it: if there is .action in the URL, it is dynamic, and the others are static. The following code is the code in the thread task processing class.

The processing interface is mainly an interface of a processing class, and the dynamic processing class and the static processing class implement it

/**
 * 处理器
 * 处理请求的接口
 *
 * @author 康有为
 * @date 2023/08/17
 */
public interface Processor {
    /** 处理请求,给出响应
     * @param request 请求
     * @param response 响应
     */
   void process(HttpServletRequest request, HttpServletResponse response);
}

 Static processing of classes is very simple. Just call the sendRedirect method of the corresponding class and return it to the front end.

/**
 * 静态处理器
 *     实现处理接口
 *
 * @author 康有为
 * @date 2023/08/17
 */
public class StaticProcessor implements Processor {

   @Override
   public void process(HttpServletRequest request, HttpServletResponse response) {
      //调用响应类的 sendRedirect方法
      response.sendRedirect();
   }

}

Dynamic processing class

Because the dynamically processed URL must end with ".action" (customized by us), we need to parse the URL and get the things in front of .action.

For example: For the request localhost:8888/User.action, after we get the User, we then splice a "Servlet", then use reflection to find the UserServlet in the corresponding directory where the servlet instance file is stored, and then call its corresponding servlet method . That's it.

 

6.servlet instance class

Here is the processing and feedback of dynamic requests from the front end. Specific servlet instances can be processed according to different businesses.

Note: The servlet instance must be placed in the specified directory "servlet", because when we process dynamic requests, we use reflection to scan the "servlet" directory, and it cannot be found in other directories.

For example, the picture below is a login servlet. Then it will be returned to the front-end if the login is successful and the session will be attached.

 

Reference article: [Tomcat] - Implementing a simple Tomcat with pure handwriting_Handwritten tomcat implements deployment function_Tudou is my favorite blog-CSDN blog

For specific detailed code, please see the source code  Kang Youwei/Handwritten Tomcat - Code Cloud - Open Source China (gitee.com)

If you like it, give it a like and support it

 

Guess you like

Origin blog.csdn.net/KangYouWei6/article/details/132447561