Java implementation of simple static dynamic http server

Write static dynamic http server

Use case diagram:

Implementation requirements:
The purpose of implementing static dynamic server is to better understand the HTTP protocol and the underlying native principle of Servlet.

Implementation description:

The user submits an http request to the startup port of the specified server through the browser. In the HttpServer, Java encapsulates the http request stream and response stream through the Socket, encapsulates the http request stream into an HttpRequest object through the HttpPrase method, and then reads the HTML, CSS and JS files in the specified directory and converts them into a file stream, The HttpResponse object encapsulated by the response flow returns the data to the browser to realize a static server similar to nginx.

By modifying the implementation class, you can configure the dynamic server of the mapping path, similar to:

  • Visit / to access / index html

Project directory:

  • -java
    • -ServerApplication
    • -handler
      • -impl
        • -DynamicHandler
        • -StaticHandler
      • -Handler
      • -HttpPrase
    • -http
    • -server
      • -HttpServer
      • -HttpWorkerThread

1. Write HttpServer class

effect:

  • -Binding port number
  • -Create a Socket object to receive the request response flow
  • -Create a new thread dedicated to processing requests and responses
/**
 * @author HaiPeng Wang
 * @date 2021/8/5 11:22
 * @Description:
 */
public class HttpServer {


    /*Port number*/
    private int port;
    
    public HttpServer(int port) {
        this.port = port;
    }

    public void start(){
        try {
            ServerSocket serverSocket = new ServerSocket();
            /*Binding port number*/
            serverSocket.bind(new InetSocketAddress(port));
            
            System.out.println("Server start in "+ port);
            /*Receive the request from the browser through an endless loop, and create a thread for him to process his request and response*/
            while (true){
                /*Establish a new connection*/
                Socket  cilentSocket = serverSocket.accept();
                cilentSocket.setSoTimeout(1000);
                System.out.println("A new connection : "+cilentSocket.getInetAddress());
                /*Create a thread for each connection*/
                new HttpWorkerThread(cilentSocket).start();
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }

}

2. Write the HttpWorkerThread class

effect:

  • -Call httpprase prase()
  • -Create HttpResponse object
  • -Call handler The handle method handles requests and responses
/**
 * @author HaiPeng Wang
 * @date 2021/8/5 11:26
 * @Description:
 */
public class HttpWorkerThread extends Thread{

    public Socket cilentSocket;

    public HttpWorkerThread(Socket cilentSocket) {
        this.cilentSocket = cilentSocket;
    }

    @Override
    public void run() {
        /*Parsing Http requests according to Http protocol*/
        try{
            System.out.println("Thread:" + this.getId());
            /*Get InputStream through Socket to create converter object*/
            HttpParse httpParse = new HttpParse(cilentSocket.getInputStream());
            /*Call the converter method to encapsulate the request message information in InputStream into HttpRequest*/
            HttpRequest httpRequest = httpParse.prase();
            /*Get the OutputStream through the Socket and encapsulate it into a response object*/
            HttpResponse httpResponse = new HttpResponse(cilentSocket.getOutputStream());
            /*Create a processor, similar to the Service method in a Servlet*/
            Handler handler = new StaticHandler();
//            Handler dynamicHandler = new DynamicHandler();
            handler.handle(httpRequest,httpResponse);

        }catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3. Write the HttpRequest class

effect:

  • -Store information in InputStream
/**
 * @author HaiPeng Wang
 * @date 2021/8/5 11:34
 * @Description:
 */
public class HttpRequest {

    /*Http Input stream*/
    private InputStream inputStream;

    /*Http Request method*/
    private String method;

    /*Http Request line*/
    private String line;

    /*Http Request header, stored in HashMap*/
    private Map<String,String> headers = new HashMap<>();

    /*Http Request body*/
    private String body;

    /*Http Request path, for example: localhost:1024/test/url, then url = "/ test/url"*/
    private String url;


    public HttpRequest(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    public InputStream getInputStream() {
        return inputStream;
    }

    public String getMethod() {
        return method;
    }

    public String getLine() {
        return line;
    }

    public Map<String, String> getHeaders() {
        return headers;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public String getHeader(String name){
        return headers.get(name);
    }

    public void addHeader(String name,String value){
        headers.put(name, value);
    }

    public void setLine(String result) {
        this.line = result;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUrl() {
        return url;
    }
}

4. Write HttpPrase class

effect:

  • -Encapsulates the InputStream and returns the HttpRequest object
    • -Encapsulation request header
    • -Encapsulate request line
    • -Encapsulation request body

Http protocol:

  • Is a hypertext transmission protocol, which transmits hypertext to the local browser based on TCP/IP protocol
  • It is a connectionless protocol: each connection only processes one request. The server processes the request and makes a response after processing. When the browser receives the response, it disconnects, which can save transmission time
  • It is a stateless protocol: it has no memory function for transaction processing

Http request message:

public class HttpParse {

    /*Http Input stream*/
    private InputStream is;

    /*Hold HttpRequest object*/
    private HttpRequest httpRequest;

    /*Convert the input stream to a string array according to / r/n*/
    private String[] requestStr;

    /*It must be created through the input stream*/
    public HttpParse(InputStream is) {
        this.is = is;
        httpRequest = new HttpRequest(is);
    }


    /**
     * Parse request line
     * @return Request line string
     */
    public HttpRequest prase() throws IOException {
        HttpRequest httpRequest = parseToString()
                .requestLine()
                .requestHeader()
                .requestBody();
        return httpRequest;
    }

    /**
     * Parse request line
     * @return
     * @throws IOException
     */
    public HttpParse requestLine() throws IOException {
        String line = requestStr[0];
        String[] str1 = line.split(" ");
        httpRequest.setLine(line);  //Set full response line
        httpRequest.setMethod(str1[0]); //Set response method
        httpRequest.setUrl(str1[1]); //Set url
        return this;
    }

    /**
     * Parse request header
     * @return Request header Map
     */
    public HttpParse requestHeader() throws IOException {
        int i = 1;
        while (i < requestStr.length && requestStr[i].equals("") ){
            String[] strings = requestStr[i].split(":");
            if (strings[0].toLowerCase().equals("host")){
                strings[1] = strings[1] + ":" +strings[2];
            }
            httpRequest.addHeader(strings[0],strings[1]);  //Add each response header to the request header
            i++;
        }
        return this;
    }

    /**
     * Parse request body
     * @return Request body string
     */
    public HttpRequest requestBody(){
        int i = requestStr.length-1;
        String body = requestStr[i];
        httpRequest.setBody(body);
        return this.httpRequest;
    }

    /**
     * Read the request data stream and convert it into a string array according to / r/n
     * @return
     * @throws IOException
     */
    public HttpParse parseToString() throws IOException {

        this.httpRequest = new HttpRequest(is);
        String result = new String();
        StringBuffer stringBuffer = new StringBuffer();
        try {
            byte[] buf = new byte[128];
            int size = 0;
            while (( size = is.read(buf,0,buf.length)) != -1) {
                for (int i = 0; i < buf.length ; i ++){
                    stringBuffer.append((char) buf[i]);
                }
            }
        }catch (SocketTimeoutException e){
            /**
             * If the browser does not receive the response data, it will not actively end the connection
             * This causes the InputStream to be an infinite stream
             * However, this will block the read method when reading
             * In this case, tsocket setSoTimeout(1000);  Set the read waiting time. If the time is exceeded, an exception will be thrown
             * At this time, you can actively end the read block, jump out, and then proceed to the next step
             * Otherwise, the browser will always be stuck in circles
             */
        }
        this.requestStr = stringBuffer.toString().split("\r\n");
        return this;
    }
}

5. Write the HttpResponse class

/**
 * @author HaiPeng Wang
 * @date 2021/8/5 15:12
 * @Description:
 */
public class HttpResponse {

    /*Enumeration classes store StatusCode and StatusDescription*/
    private HttpStatus httpStatus;

    /*Response header information*/
    private Map<String,String> headers = new HashMap<>();

    /*ContentType Information for storing file suffixes and*/
    private static Map<String,String> type = new HashMap<>();




    static{
        /**
         * Initialize the type object and store the corresponding relationship between suffix name and contentType
         */
        type.put("css","text/css;charset=utf-8");
        type.put("js","text/js;charset=utf-8");
        type.put("html","text/html;charset=utf-8");
        type.put("json","text/json;charset=utf-8");
        type.put("png","application/x-png");
    }

    {
        /**
         * Initializing headers is equivalent to setting default values
         */
        headers.put("Content-Type","text/html;charset=utf-8 ");
//        headers.put("Content-Length","4096");
        /**
         * Error net::ERR_CONTENT_LENGTH_MISMATCH
         * I thought it would be a dead length, but it didn't work
         * If not, check the response The meaning of the header field length. It is found that general browsers will automatically calculate the length
         * content length The length of is the transmission length of the message entity. One is that the transmission length is different from the entity length
         * The message entity here represents the response body in bytes
         * Then I won't do it here. Let him do it himself
         * 
         * If the length of content length is greater than the actual length, it will be blocked and an error will be reported
         * If less than, it will be truncated
         * Therefore, it must be accurate. Some browsers have calculation function, but some older browsers actually have no calculation function
         */
        headers.put("Connection","close");
    }

    private OutputStream os;


    public HttpResponse(OutputStream os) {
        this.os = os;
    }

    public void setHttpStatus(HttpStatus httpStatus) {
        this.httpStatus = httpStatus;
    }

    public OutputStream getOs() {
        return os;
    }

    public void addHeader(String name,String value){
        headers.put(name,value);
    }

    public String getHeader(String name){
        return headers.get(name);
    }

    public void setContentType(String value){
        headers.put("Content-Type",value);
    }
    public String getContentType(){
        return this.headers.get("Content-Type");
    }

    public void setContentLength(Integer value){
        headers.put("Content-Length",value.toString());
    }
    public String getContentLength(){
        return this.headers.get("Content-Length");
    }

    public Map<String, String> getType() {
        return type;
    }

    /**
     * Generates a response string from the HttpResponse object
     * @return
     */
    public String loadResponse(){
        String lankLine = "\r\n";
        String blank = " ";
        String result = "";
        String colon = ":";
        result += "HTTP/1.1" + blank + this.httpStatus.getCode() + blank + this.httpStatus.getMsg()+ lankLine;
        Set<String> keySet = this.headers.keySet();
        for (String key : keySet){
            result += key + colon + this.headers.get(key) + lankLine ;
        }
        result += lankLine;
        return result;
    }

    /**
     * Writes the string back to the front end
     * @param str
     * @throws IOException
     */
    public void write(String str) throws IOException {
        os.write(str.getBytes(StandardCharsets.UTF_8));
        os.flush();
        os.close();
    }

    /**
     * Write back to the front end in the form of string + byte stream
     * @param str
     * @param bytes
     * @throws IOException
     */
    public void write(String str,byte[] bytes) throws IOException {
        os.write(str.getBytes(StandardCharsets.UTF_8));
        os.write(bytes);
        os.flush();
        os.close();
    }

    /**
     * Convert the object into json and write it back to the front end
     * @param t
     * @param <T>
     * @throws IOException
     */
    public<T> void writeBody(T t) throws IOException {
        this.httpStatus = HttpStatus.SC_OK;
        this.setContentType("application/json");
        String result = loadResponse();
        String json = "{lalalala}";
        result += json;
        write(result);
    }
}

The essence of Http response message and request message is a agreed string

6. Write HttpStatus enumeration class

effect:

  • -Indicates the response code and description
/**
 * @author HaiPeng Wang
 * @date 2021/8/5 15:13
 * @Description:
 */
public enum HttpStatus {

    NOT_FOUNT(404,"NotFound"),
    SC_OK(200,"OK"),
    BAD_REQUEST(200,"BadRequest"),
    SERVER_ERROR(400,"ServerError");
    private int code;

    private String msg;

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    HttpStatus(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

7. Write Handler interface

effect:

  • -Here, a flexible policy pattern is used. You only need to change the implementation class of Handler to realize static and dynamic conversion
/**
 * @author HaiPeng Wang
 * @date 2021/8/5 15:10
 * @Description:Request processing interface
 */
public interface Handler {


    public void handle(HttpRequest request, HttpResponse httpResponse) throws IOException;
}

8. Write the StaticHandler implementation class

effect:

  • -Read the file in the specified path and transfer the file to the browser
  • -Set different content types according to different file suffixes
  • -Write error response
/**
 * @author HaiPeng Wang
 * @date 2021/8/5 15:11
 * @Description:Static server implementation class
 */
public class StaticHandler implements Handler {


    /**
     * Parse the file and return the file to the front end
     * @param request
     * @param response
     */
    @Override
    public void handle(HttpRequest request, HttpResponse response) throws IOException {
        sendFile(request,response);
    }

    public void error(HttpRequest request,HttpResponse response) throws IOException {
        response.setHttpStatus(HttpStatus.NOT_FOUNT);
        String result = response.loadResponse();
        response.write(result);
    }

    public void sendFile(HttpRequest request,HttpResponse response) throws IOException {
        String staicPath = "W:\\Desktop files\\2021 Summer Yunze\\minutes of the meeting\\20210804-Java-Tomcat\\20210804-Java-Tomcat\\html";
        /*Replace separator in Url*/
        String url = request.getUrl().replace("/",File.separator);
        String path = staicPath + url;
        String[] strs = url.split("\\.");
        String contentType = response.getType().get(strs[1]);
        response.setContentType(contentType);
        File file = new File(path);
        FileInputStream inputStream = null;
        try {
            inputStream = new FileInputStream(file);
        }catch (Exception e){
            error(request,response);
        }
        byte[] bytes = inputStream.readAllBytes();
        response.setHttpStatus(HttpStatus.SC_OK);
        response.write(response.loadResponse(),bytes);
    }
}

9. Write the DynamicHandler implementation class

effect:

  • -Realize dynamic access according to mapping
  • -Small details, the mapping information of each server should be shared by each server object, so that these mappings have the same life cycle as the server object
/**
 * @author HaiPeng Wang
 * @date 2021/8/8 19:50
 * @Description:Dynamic implementation class
 */
public class DynamicHandler implements Handler {

    private Map<String,String> maps = new HashMap<>();
    {
        maps.put("/cgxz","/cgxz/index.html");
        maps.put("/css/base.css","/css/base.css");
        maps.put("/css/header.css","/css/header.css");
        maps.put("/css/footer.css","/css/footer.css");
        maps.put("/css/index.css","/css/index.css");
        maps.put("/images/banner.png","/images/banner.png");
        maps.put("/images/img1.png","/images/img1.png");
        maps.put("/js/index.js","/js/index.js");
    }

    @Override
    public void handle(HttpRequest request, HttpResponse response) throws IOException {
        String staicPath = "W:\\Desktop files\\2021 Summer Yunze\\minutes of the meeting\\20210804-Java-Tomcat\\20210804-Java-Tomcat\\html";
        String url = null;
        try {
            url = maps.get(request.getUrl()).replace("/", File.separator);
        }catch (NullPointerException e){
            error(request,response);
        }
        if (url == "" || url == null){
            error(request,response);
        }
        String path = staicPath + url;
        String[] strs = url.split("\\.");
        String contentType = response.getType().get(strs[1]);
        response.setContentType(contentType);
        File file = new File(path);
        FileInputStream inputStream = null;
        try {
            inputStream = new FileInputStream(file);
        }catch (Exception e){
            error(request,response);
        }
        byte[] bytes = inputStream.readAllBytes();
        response.setHttpStatus(HttpStatus.SC_OK);
        response.write(response.loadResponse(),bytes);
    }

    public void addMapping(String realPath,String mapping){
        maps.put(mapping,realPath);
    }

    public void error(HttpRequest request,HttpResponse response) throws IOException {
        response.setHttpStatus(HttpStatus.NOT_FOUNT);
        String result = response.loadResponse();
        response.write(result);
    }
}

10. Write startup class

public class ServerApplication {

    public static void main(String[] args) {
        /*args Is a string array of command line parameters. Here, you can obtain the corresponding configuration according to the command line parameters*/
        if (args.length == 0){
            /*Give the form of adding port*/
            System.out.println("Usage:Java -jar static-server.jar <port>");
        }
        int port = Integer.parseInt(args[0]);
        HttpServer server = new HttpServer(port);
        server.start();
    }
}

11. idea method for configuring command line parameters

1,

2. Configure at 1024

Keywords: Java Nginx Tomcat http IDEA

Added by sametch on Sat, 25 Dec 2021 04:12:37 +0200