手寫(xiě)tomcat

名稱(chēng)∶Minicat
Minicat要做的事情∶作為一個(gè)服務(wù)器軟件提供服務(wù)的,也即我們可以通過(guò)瀏覽器客戶(hù)端發(fā)送http請(qǐng)求,Minicat可以接收到請(qǐng)求進(jìn)行處理,處理之后的結(jié)果可以返回瀏覽器客戶(hù)端。
1)提供服務(wù),接收請(qǐng)求(Socket通信)
2)請(qǐng)求信息封裝成Request對(duì)象(Response對(duì)象)
3)客戶(hù)端請(qǐng)求資源,資源分為靜態(tài)資源(html)和動(dòng)態(tài)資源(Servlet )
4)資源返回給客戶(hù)端瀏覽器
我們遞進(jìn)式完成以上需求,提出V1.0、V2.0、V3.0版本的需求
V1.0需求∶瀏覽器請(qǐng)求nttp/localhost8080返回一個(gè)固定的字符串到頁(yè)面"Hello Minicat!"
V2.0需求∶封裝Request和Response對(duì)象,返回html靜態(tài)資源文件
V3.0需求∶可以請(qǐng)求動(dòng)態(tài)資源(Servlet)完成上述三個(gè)版本后,我們的代碼如下

  • Bootstrap 啟動(dòng)類(lèi)
package com.study.server;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 * @author Qi XueSong
 * Minicat 的主類(lèi)
 */
public class Bootstrap {

    /**定義socket監(jiān)聽(tīng)的端口號(hào)*/
    private int port = 8080;

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    Map<String,HttpServlet> map = new HashMap<>();

    private void start() throws Exception {

        // 加載解析相關(guān)的配置,web.xml
        loadServlet();

        int corePoolSize = 10;
        int maximumPoolSize = 50;
        long keepAliveTime = 100L;
        TimeUnit unit = TimeUnit.SECONDS;
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50);
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            corePoolSize,
            maximumPoolSize,
            keepAliveTime,
            unit,
            workQueue,
            threadFactory,
            handler
        );

        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("Minicat start on port:" + port);
         /*
            完成Minicat 1.0版本
            需求:瀏覽器請(qǐng)求http://localhost:8080,返回一個(gè)固定的字符串到頁(yè)面"Hello Minicat!"
         */
        /*while (true){
            Socket socket = serverSocket.accept();
            // 有了socket , 接收到請(qǐng)求,獲取輸出流
            OutputStream outputStream = socket.getOutputStream();
            String data = "Hello Minicat!";
            String responseText = HttpProtocolUtil.getHttpHeader200(data.getBytes().length)+data;
            outputStream.write(responseText.getBytes());
            socket.close();
        }*/

        /**
         * 完成Minicat 2.0版本
         * 需求:封裝Request和Response對(duì)象,返回html靜態(tài)資源文件
         */
        /*while (true){
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            // 封裝Request對(duì)象和Response對(duì)象
            Request request = new Request(inputStream);
            Response response = new Response(socket.getOutputStream());
            String url = request.getUrl();
            response.outputHtml(url);
        }*/

        /**
         * 完成Minicat 3.0版本
         * 需求:可以請(qǐng)求動(dòng)態(tài)資源(Servlet)
         */
        /*while (true){
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            // 封裝Request對(duì)象和Response對(duì)象
            Request request = new Request(inputStream);
            Response response = new Response(socket.getOutputStream());
            String url = request.getUrl();
            // 靜態(tài)資源處理
            if(map.get(request.getUrl()) == null){
                response.outputHtml(url);
            } else {
                // 動(dòng)態(tài)資源servlet請(qǐng)求
                map.get(url).service(request,response);
            }
            socket.close();
        }*/

        /**
         * 多線(xiàn)程改造(不使用線(xiàn)程池)
         */
        /*while (true){
            Socket socket = serverSocket.accept();
            RequestProcessor requestProcessor = new RequestProcessor(socket, map);
            requestProcessor.start();
        }*/


        System.out.println("=========>>>>>>使用線(xiàn)程池進(jìn)行多線(xiàn)程改造");
        /*
            多線(xiàn)程改造(使用線(xiàn)程池)
         */
        while (true){
            Socket socket = serverSocket.accept();
            RequestProcessor requestProcessor = new RequestProcessor(socket, map);
            threadPoolExecutor.execute(requestProcessor);
        }

    }

    private void loadServlet() {
        InputStream resourceAsStream = Bootstrap.class.getClassLoader().getResourceAsStream("web.xml");
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> list = rootElement.selectNodes("http://servlet");
            for (int i = 0; i < list.size(); i++) {
                Element element =  list.get(i);
                // <servlet-name>study</servlet-name>
                Element servletNameElement = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletNameElement.getStringValue();
                // <servlet-class>com.study.server.StudyServlet</servlet-class>
                Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletclassElement.getStringValue();

                // 根據(jù)servlet-name的值找到url-pattern
                Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
                map.put(urlPattern,(HttpServlet) Class.forName(servletClass).newInstance());
            }
        } catch (DocumentException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * minicat 的啟動(dòng)入口
     */
    public static void main(String[] args) {
        Bootstrap bootstrap = new Bootstrap();
        try {
            // 啟動(dòng) minicat
            bootstrap.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • Http協(xié)議工具類(lèi)
package com.study.server;

/**
 * http協(xié)議工具類(lèi),主要是提供響應(yīng)頭信息,這里我們只提供200和404的情況
 * @author Qi XueSong
 */
public class HttpProtocolUtil {

    /**
     * 為響應(yīng)碼200提供請(qǐng)求頭信息
     */
    public static String getHttpHeader200(long contentLength){
        return "HTTP/1.1 200 OK \n" +
                "Content-Type: text/html \n" +
                "Content-Length: " + contentLength + " \n" +
                "\r\n";
    }

    /**
     * 為響應(yīng)碼404提供請(qǐng)求頭信息(此處也包含了數(shù)據(jù)內(nèi)容)
     */
    public static String getHttpHeader404(){
        String str404 = "<h1>404 NOT Found</h1>";
        return "HTTP/1.1 404 NOT Found \n" +
                "Content-Type: text/html \n" +
                "Content-Length: " + str404.getBytes().length + " \n" +
                "\r\n" + str404;
    }

}
  • Request封裝類(lèi)
package com.study.server;

import java.io.IOException;
import java.io.InputStream;

/**
 * 把請(qǐng)求信息封裝為Request對(duì)象(根據(jù)InputSteam輸入流封裝)
 * @author Qi XueSong
 */
public class Request {

    private String method; // 請(qǐng)求方式,比如GET/POST
    private String url;  // 例如 /,/index.html

    private InputStream inputStream;  // 輸入流,其他屬性從輸入流中解析出來(lái)


    public String getMethod() {
        return method;
    }

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

    public String getUrl() {
        return url;
    }

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

    public InputStream getInputStream() {
        return inputStream;
    }

    public void setInputStream(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    public Request() {
    }


    // 構(gòu)造器,輸入流傳入
    public Request(InputStream inputStream) throws IOException {
        this.inputStream = inputStream;

        // 從輸入流中獲取請(qǐng)求信息
        int count = 0;
        while (count == 0) {
            count = inputStream.available();
        }

        byte[] bytes = new byte[count];
        inputStream.read(bytes);

        String inputStr = new String(bytes);
        // 獲取第一行請(qǐng)求頭信息
        String firstLineStr = inputStr.split("\\n")[0];  // GET / HTTP/1.1

        String[] strings = firstLineStr.split(" ");

        this.method = strings[0];
        this.url = strings[1];

        System.out.println("=====>>method:" + method);
        System.out.println("=====>>url:" + url);

    }
}
  • Response封裝類(lèi)
package com.study.server;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * 封裝Response對(duì)象,需要依賴(lài)于OutputStream
 * 該對(duì)象需要提供核心方法,輸出html
 *
 * @author Qi XueSong
 */
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());
    }


    /**
     *
     * @param path  url,隨后要根據(jù)url來(lái)獲取到靜態(tài)資源的絕對(duì)路徑,進(jìn)一步根據(jù)絕對(duì)路徑讀取該靜態(tài)資源文件,最終通過(guò)
     *                  輸出流輸出
     *              /-----> classes
     */
    public void outputHtml(String path) throws IOException {
        // 獲取靜態(tài)資源文件的絕對(duì)路徑
        String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path);

        // 輸入靜態(tài)資源文件
        File file = new File(absoluteResourcePath);
        if(file.exists() && file.isFile()) {
            // 讀取靜態(tài)資源文件,輸出靜態(tài)資源
            StaticResourceUtil.outputStaticResource(new FileInputStream(file),outputStream);
        }else{
            // 輸出404
            output(HttpProtocolUtil.getHttpHeader404());
        }

    }

}
  • 靜態(tài)資源請(qǐng)求處理工具類(lèi)
package com.study.server;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class StaticResourceUtil {

    /**
     * 獲取靜態(tài)資源文件的絕對(duì)路徑
     * @author Qi XueSong
     */
    public static String getAbsolutePath(String path) {
        String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
        return absolutePath.replaceAll("\\\\","/") + path;
    }


    /**
     * 讀取靜態(tài)資源文件輸入流,通過(guò)輸出流輸出
     */
    public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {

        int count = 0;
        while(count == 0) {
            count = inputStream.available();
        }

        int resourceSize = count;
        // 輸出http請(qǐng)求頭,然后再輸出具體內(nèi)容
        outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes());

        // 讀取內(nèi)容輸出
        long written = 0 ;// 已經(jīng)讀取的內(nèi)容長(zhǎng)度
        int byteSize = 1024; // 計(jì)劃每次緩沖的長(zhǎng)度
        byte[] bytes = new byte[byteSize];

        while(written < resourceSize) {
            if(written  + byteSize > resourceSize) {  // 說(shuō)明剩余未讀取大小不足一個(gè)1024長(zhǎng)度,那就按真實(shí)長(zhǎng)度處理
                byteSize = (int) (resourceSize - written);  // 剩余的文件內(nèi)容長(zhǎng)度
                bytes = new byte[byteSize];
            }

            inputStream.read(bytes);
            outputStream.write(bytes);

            outputStream.flush();
            written+=byteSize;
        }
        inputStream.close();
        outputStream.close();
    }
}

動(dòng)態(tài)資源請(qǐng)求

  • Servlet接口定義
package com.study.server;

public interface Servlet {

    void init() throws Exception;

    void destory() throws Exception;

    void service(Request request, Response response) throws Exception;
}
  • HttpServlet抽象類(lèi)定義
package com.study.server;

/**
 * @author Qi XueSong
 */
public abstract class HttpServlet implements Servlet{

    public abstract void doGet(Request request, Response response);

    public abstract void doPost(Request request, Response response);

    @Override
    public void service(Request request, Response response) throws Exception {
        if("GET".equals(request.getMethod())){
            doGet(request,response);
        } else {
            doPost(request,response);
        }
    }
}
  • 業(yè)務(wù)類(lèi)Servlet定義StudyServlet
package com.study.server;

import java.io.IOException;

/**
 * @author Qi XueSong
 */
public class StudyServlet extends HttpServlet {
    @Override
    public void doGet(Request request, Response response) {
        try {
            Thread.sleep(10000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String content = "<h1>StudyServlet get</h1>";
        try {
            response.output(HttpProtocolUtil.getHttpHeader200(content.getBytes().length)+content);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doPost(Request request, Response response) {
        String content = "<h1>StudyServlet post</h1>";
        try {
            response.output(HttpProtocolUtil.getHttpHeader200(content.getBytes().length)+content);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void init() throws Exception {

    }

    @Override
    public void destory() throws Exception {

    }
}
  • 多線(xiàn)程改造封裝的RequestProcessor類(lèi)
package com.study.server;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.Map;

/**
 * @author qixuesong
 * @version 1.0
 * @since 2020/10/7
 */
public class RequestProcessor extends Thread {

    private Socket socket;

    private Map<String,HttpServlet> map;

    public RequestProcessor(Socket socket, Map<String, HttpServlet> map) {
        this.socket = socket;
        this.map = map;
    }

    public RequestProcessor() {
    }

    @Override
    public void run() {
        try {
            InputStream inputStream = socket.getInputStream();
            // 封裝Request對(duì)象和Response對(duì)象
            Request request = new Request(inputStream);
            Response response = new Response(socket.getOutputStream());
            String url = request.getUrl();
            // 靜態(tài)資源處理
            if(!map.containsKey(request.getUrl())){
                response.outputHtml(url);
            } else {
                // 動(dòng)態(tài)資源servlet請(qǐng)求
                map.get(url).service(request,response);
            }
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • web.xml
<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
    <servlet>
        <servlet-name>study</servlet-name>
        <servlet-class>com.study.server.StudyServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>study</servlet-name>
        <url-pattern>/study</url-pattern>
    </servlet-mapping>
</web-app>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容