實現一個簡陋的服務器(理解服務器的核心容器以及servlet工作原理)

目的

  • 理解服務器的核心容器以及servlet工作原理。

服務器實現步驟

  1. 瀏覽器發起的每個請求,都需要解析和響應,對請求報文解析,和響應報文部分內容封裝請求對象和響應對象(HttpServletRequest和HttpServletResponse都是接口,由org.apache.catalina.connector(Tomcat)包下面的RequestFacade和ResponseFacade實現並進行實例化)。

  2. 定義處理服務類的標準格式(MyServlet)

  3. 加載核心容器(MyServlet容器),開啓監聽

  4. 服務器端多線程處理

  5. ps:socket是對TCP/IP協議的封裝。
    在這裏插入圖片描述

req和resp的定義

  • 遵循http協議,解析請求、響應報文。
  • MyReq.java
public class MyReq {
    private String method;
    private String url;

    public MyReq(InputStream is) {
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        try {
            String reqHeader = br.readLine();
            String[] reqHeaderArr = reqHeader.split(" ");
            this.method = reqHeaderArr[0];
            this.url = reqHeaderArr[1];
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String getMethod() {
        return method;
    }

    public String getUrl() {
        return url;
    }
}
  • MyResp.java
public class MyResp {
    public static String respHeader = "HTTP/1.1 200 OK\r\n" +
                                        "Content-Type:text/html;charset=utf-8\r\n" +
                                        "\r\n";
    private OutputStream writer;

    public MyResp(OutputStream os) {
        this.writer = os;
    }

    public OutputStream getWriter() {
        return writer;
    }
}

MyServlet模板的定義

  • MyServlet.java
public abstract class MyServlet {
    public void doService(MyReq req, MyResp resp) {
        String reqMethod = req.getMethod();
        if ("GET".equals(reqMethod)) {
            doGet(req, resp);
        } else {
            doPost(req, resp);
        }
    }

    public abstract void doGet(MyReq req, MyResp resp);
    public abstract void doPost(MyReq req, MyResp resp);
}
  • 編寫兩個類繼承MyServlet模板,以作測試
public class LoginServlet extends MyServlet {

    @Override
    public void doGet(MyReq req, MyResp resp) {
        OutputStream respWriter = resp.getWriter();
        try {
            respWriter.write(MyResp.respHeader.getBytes());
            respWriter.write("login ok!".getBytes());
            respWriter.flush();
            respWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doPost(MyReq req, MyResp resp) {
        doGet(req, resp);
    }
}
public class RegistServlet extends MyServlet {

    @Override
    public void doGet(MyReq req, MyResp resp) {
        OutputStream respWriter = resp.getWriter();
        try {
            respWriter.write(MyResp.respHeader.getBytes());
            respWriter.write("register ok!".getBytes());
            respWriter.flush();
            respWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doPost(MyReq req, MyResp resp) {
        doGet(req, resp);
    }
}

加載核心容器

  • 配置文件:myMapping.properties
servlet.one.url=/login
servlet.one.class=com.myserver.servlet.LoginServlet

servlet.two.url=/regist
servlet.two.class=com.myserver.servlet.RegistServlet
  • MyServer.java
public class MyServer {
    static Map<String, MyServlet> myMapping = new HashMap<>();// 核心容器
    static Properties prop = new Properties();

    public static void myInit() {
        try {
            // 讀取配置文件
            prop.load(MyServer.class.getResourceAsStream("/myMapping.properties"));
            Set<Object> set = prop.keySet();

            set.forEach(k -> {
                if (k.toString().contains("url")) {
                    // 獲得url的servlet的對應
                    String url = prop.getProperty(k.toString());
                    String classKey = k.toString().replace("url", "class");
                    String className = prop.getProperty(classKey);
                    try {
                        // 通過類名獲得實例
                        MyServlet myms = (MyServlet) Class.forName(className).newInstance();
                        // 將url與myServlet的映射關係存入映射表中
                        myMapping.put(url, myms);

                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void serverBegin() {
        try {
            ServerSocket ss = new ServerSocket(8021);
            System.out.println("開始監聽 8021 端口,等待訪問...");
            while (true) {
                // 同時滿足多人訪問,多線程
                Socket accept = ss.accept();
                Thread myThread = new Thread(new MyProccess(accept));
                myThread.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 啓動服務器
     * @param args
     */
    public static void main(String[] args) {
        MyServer.myInit();
        MyServer.serverBegin();
    }
}

服務器端多線程訪問處理

  • MyProccess.java
public class MyProccess implements Runnable {
    private Socket accept;

    public MyProccess(Socket accept) {
        this.accept = accept;
    }

    @Override
    public void run() {
        try {
            // 服務器針對瀏覽器的每一次請求創建req和resp對象
            MyReq req = new MyReq(accept.getInputStream());
            MyResp resp = new MyResp(accept.getOutputStream());
            // myMapping即爲核心容器
            MyServlet myServlet = MyServer.myMapping.get(req.getUrl());
            if (myServlet != null) {
                myServlet.doService(req, resp);
            } else {
                OutputStream respWriter = resp.getWriter();
                respWriter.write(MyResp.respHeader.getBytes());
                respWriter.write("404 not found!".getBytes());
                respWriter.flush();
                respWriter.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

目錄結構

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章