MiniCat Basic Introduction

We will hand-write a Mini version of Tomcat. What we implement is a server software that can receive HTTP requests from browser clients, process them through MiniCat, and return the processing results to the browser client.

  • Provide service, accept requests (Socket communication)
  • Request information encapsulated as Request object (Response object)
  • Client requests resources, resources divided into static resources (HTML) and dynamic resources (Servlet)
  • Return resources to client browser

Response Request Response Class

Defined a public class named Response, used to encapsulate logic for sending data to client (such as HTML, text, etc.).

public class Response {
    private OutputStream outputStream;
    public Response() {
    }
    public Response(OutputStream outputStream) {
        this.outputStream = outputStream;
    }
    public void output(String content) throws IOException {
        outputStream.write(content.getBytes());
    }
    public void outputHtml(String path) throws IOException {
        String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path);
        File file = new File(absoluteResourcePath);
        if (file.exists() && file.isFile()) {
            StaticResourceUtil.outputStaticResource(new FileInputStream(file), outputStream);
        } else {
            output(HttpProtocoUtil.getHttpHeader404());
        }
    }
}

OutputStream is the base class of Java I/O, used to output byte data to external (such as client’s socket). Its typical usage scenario is writing response content to browser in Web service.

Main responsibilities of Response class:

  • Encapsulate output stream: Take OutputStream as the core resource of the class, used to write data to client
  • Text output: Can directly output a string to client (such as returning HTTP response headers and body)
  • HTML output: Read static HTML file from file system and write back to client
  • 404 handling: Automatically return standard 404 response when file does not exist

Servlet Interface

public interface Servlet {
    void init() throws Exception;
    void destroy() throws Exception;
    void service(Request request, Response response) throws Exception;
}

StaticResourceUtil Static Resources

public class StaticResourceUtil {
    public static String getAbsolutePath(String path) {
        String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
        return absolutePath.replace("\\\\", "/") + path;
    }
    public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {
        int count = 0;
        while (count == 0) {
            count = inputStream.available();
        }
        int resourceSize = count;
        outputStream.write(HttpProtocoUtil.getHttpHeader200(resourceSize).getBytes());
        long written = 0;
        int byteSize = 1024;
        byte[] bytes = new byte[byteSize];
        while (written < resourceSize) {
            if (written + byteSize > resourceSize) {
                byteSize = (int) (resourceSize - written);
                bytes = new byte[byteSize];
            }
            inputStream.read(bytes);
            outputStream.write(bytes);
            outputStream.flush();
            written += byteSize;
        }
    }
}

Get absolute path of resource: getAbsolutePath gets the current class’s classpath root directory path, gets the actual path, finally concatenates path after classpath, returns absolute path of static resource.

Output static resource content: In outputStaticResource, inputStream.available() tries to get file length, but using while (count == 0) waiting for it to be non-zero is unstable. available() is not equivalent to “file size”. It is “currently readable bytes”, not suitable for estimating total resource length.

Overall summary:

  • Class name: StaticResourceUtil, utility class
  • Function: Get resource path, output file content to HTTP
  • Highlight: Simulate Web Server sending HTML and other static files
  • Key issues: Misuse of available(), not checking read byte count, frequent array allocation
  • Optimization direction: Use readAllBytes() or while(read != -1) pattern instead of original reading logic

WzkServlet Implementation Class

This is the usage of previous content, established our own Servlet, then start the service!

public class WzkServlet extends HttpServlet {
    @Override
    public void doGet(Request request, Response response) {
        String content = "<h1>WzkServlet GET Hello World!</h1>";
        try {
            response.output(HttpProtocoUtil.getHttpHeader200(content.getBytes().length) + content);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public void doPost(Request request, Response response) {
        String content = "<h1>WzkServlet POST Hello World!</h1>";
        try {
            response.output(HttpProtocoUtil.getHttpHeader200(content.getBytes().length) + content);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public void init() throws Exception {
    }
    @Override
    public void destroy() throws Exception {
    }
}

Resource Files

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>wzk-icu</title>
</head>
<body>
<h1>
    wzk-icu Test Page!
</h1>
</body>
</html>

web.xml

<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
    <servlet>
        <servlet-name>wzkicu</servlet-name>
        <servlet-class>icu.wzk.WzkServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>wzkicu</servlet-name>
        <url-pattern>/wzkicu</url-pattern>
    </servlet-mapping>
</web-app>

Startup Test

After starting the service, access port 8080. You can see the corresponding content output in the console, and the browser successfully displays the page.