手写实现迷你版 Tomcat

  Minicat要做的事情:作为⼀个服务器软件提供服务的,也即我们可以通过浏览器客户端发送http请求,Minicat可以接收到请求进行处理,处理之后的结果可以返回浏览器客户端。

整体思路:
(1)提供服务,接收请求(Socket通信)
(2)请求信息封装成Request对象(Response对象)
(3)客户端请求资源,资源分为静态资源(html)和动态资源(Servlet)
(4)资源返回给客户端浏览器

我们递进式完成以上需求,提出V1.0、V2.0、V3.0版本的需求:

  • V1.0需求:浏览器请求http://localhost:8080,返回⼀个固定的字符串到页面"Hello Minicat!"
  • V2.0需求:封装Request和Response对象,返回html静态资源⽂件
  • V3.0需求:可以请求动态资源(Servlet)

一、V1.0版本

需求说明:浏览器请求http://localhost:8080,返回⼀个固定的字符串到页面"Hello Minicat!"

1、创建一个Maven项目Minicat

手写实现迷你版 Tomcat_第1张图片
2、修改POM文件


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>

    <groupId>com.cydgroupId>
    <artifactId>MinicatartifactId>
    <version>1.0-SNAPSHOTversion>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <version>3.1version>
                <configuration>
                    <source>1.8source>
                    <target>1.8target>
                    <encoding>utf-8encoding>
                configuration>
            plugin>
        plugins>
    build>

project>

3、迷你版Tomcat入口(Java类的main函数)

【在分析Tomcat源码时,也能发现Tomcat的入口也是一个main函数】

public class Bootstrap {
     

    private int port = 8080;

    /**
     * Minicat启动需要初始化展开的一些操作
     * 

* 完成Minicat 1.0版本 * 需求:浏览器请求http://localhost:8080,返回一个固定的字符串到页面"Hello Minicat!" */ private void start() throws IOException { ServerSocket serverSocket = new ServerSocket(port); System.out.println("=====>>>Minicat start on port:" + port); while (true) { // 有了socket,接收到请求,获取输出流 Socket socket = serverSocket.accept(); OutputStream outputStream = socket.getOutputStream(); outputStream.write("Hello Minicat!".getBytes()); socket.close(); } } public static void main(String[] args) { try { Bootstrap bootstrap = new Bootstrap(); bootstrap.start(); } catch (IOException e) { e.printStackTrace(); } } }

  执行main函数,启动服务器,浏览器输入 http://localhost:8080 进行方法,发现浏览器出现了ERR_INVALID_HTTP_RESPONSE,这是因为浏览器发送的是HTTP请求,所以请求消息和应答消息都必须遵循HTTP规范(请求头+请求体/应答头+应答体),我们这边直接往socket输出流中输出的字符串是不符合HTTP规范的。

手写实现迷你版 Tomcat_第2张图片
那么我们应该输出什么样的数据呢?可以打开一个浏览器的控制台,访问www.baidu.com

手写实现迷你版 Tomcat_第3张图片
  从图中我们可以看出,向浏览器输出内容,需要先由Response Headers(应答头),该应答头首行必须是HTTP/1.1 200 OK,然后再加上一些key:value格式的数据。下面我们对输出信息进行改造,代码如下:

Bootstrap

public class Bootstrap {
     

    private int port = 8080;

    /**
     * Minicat启动需要初始化展开的一些操作
     * 

* 完成Minicat 1.0版本 * 需求:浏览器请求http://localhost:8080,返回一个固定的字符串到页面"Hello Minicat!" */ private void start() throws IOException { ServerSocket serverSocket = new ServerSocket(port); System.out.println("=====>>>Minicat start on port:" + port); while (true) { // 有了socket,接收到请求,获取输出流 Socket socket = serverSocket.accept(); OutputStream outputStream = socket.getOutputStream(); String contentText = "Hello Minicat!"; String text = HttpProtocolUtil.getHeader200(contentText.length()) + contentText; outputStream.write(text.getBytes()); socket.close(); } } public static void main(String[] args) { try { Bootstrap bootstrap = new Bootstrap(); bootstrap.start(); } catch (IOException e) { e.printStackTrace(); } } }

HttpProtocolUtil

/**
 * http协议工具类,主要是提供响应头信息,这里我们只提供200和404的情况
 */
public class HttpProtocolUtil {
     

    /**
     * 为响应码200提供请求头信息(这里只输出几个必要的信息)
     */
    public static String getHeader200(int contentLength) {
     
        return "HTTP/1.1 200 OK" + "\n" +
                "Content-Type: text/html" + "\n" +
                "Content-Length: " + contentLength + "\n" +
                "\r\n";
    }

    /**
     * 为响应码404提供请求头信息(此处也包含了数据内容)
     */
    public static String getHeader404() {
     
        String str404 = "

404 not found

"
; return "HTTP/1.1 404 NOT Found" + "\n" + "Content-Type: text/html" + "\n" + "Content-Length: " + str404.length() + "\n" + "\r\n" + str404; } }

测试:
手写实现迷你版 Tomcat_第4张图片

二、V2.0版本

V2.0需求:封装Request和Response对象,返回html静态资源文件

Request

public class Request {
     
    /**
     * 请求方式
     */
    private String method;

    /**
     * 请求url
     */
    private String url;

    public String getMethod() {
     
        return method;
    }

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

    public String getUrl() {
     
        return url;
    }

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

    public Request() {
     

    }

    /**
     * 构造器,传入输入流
     * 

* 获取请求头信息,封装请求方式和请求url * * @param inputStream * @throws IOException */ public Request(InputStream inputStream) throws IOException { // 从输入流中获取请求信息 int count = 0; while (count == 0) { count = inputStream.available(); } byte[] bytes = new byte[count]; inputStream.read(bytes); String inputStr = new String(bytes); // 获取第一行请求头信息(GET / HTTP/1.1) String firstLineStr = inputStr.split("\\n")[0]; String[] strings = firstLineStr.split(" "); method = strings[0]; url = strings[1]; System.out.println("=====>>method:" + method); System.out.println("=====>>url:" + url); } }

Response

public class Response {
     

    private OutputStream outputStream;

    public OutputStream getOutputStream() {
     
        return outputStream;
    }

    public void setOutputStream(OutputStream outputStream) {
     
        this.outputStream = outputStream;
    }

    public Response() {
     

    }

    public Response(OutputStream outputStream) {
     
        this.outputStream = outputStream;
    }

    /**
     * @param url url,随后要根据url来获取到静态资源的绝对路径,进一步根据绝对路径读取该静态资源文件,最终通过输出流输出
     *            /-----> classes
     */
    public void outPutHtml(String url) throws IOException {
     
        // 获取静态资源文件的绝对路径
        String absolutePath = StaticResourceUtil.getAbsolutePath(url);

        // 输入静态资源文件
        File file = new File(absolutePath);
        if (file.exists() && file.isFile()) {
     
            // 读取静态资源文件,输出静态资源
            StaticResourceUtil.outputStaticResource(new FileInputStream(file), outputStream);
        } else {
     
            // 输出404
            output(HttpProtocolUtil.getHeader404());
        }
    }

    /**
     * 使用输出流输出指定字符串
     *
     * @param content 输出的字符串内容
     * @throws IOException
     */
    public void output(String content) throws IOException {
     
        outputStream.write(content.getBytes());
    }
}

StaticResourceUtil

public class StaticResourceUtil {
     

    /**
     * 获取静态资源文件的绝对路径
     *
     * @param url
     * @return
     */
    public static String getAbsolutePath(String url) {
     
        String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
        return absolutePath.replaceAll("\\\\", "/") + url;
    }

    /**
     * 读取静态资源文件输入流,通过输出流输出
     */
    public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {
     
        int count = 0;
        while (count == 0) {
     
            count = inputStream.available();
        }

        int resourceSize = count;
        // 输出http请求头,然后再输出具体内容
        outputStream.write(HttpProtocolUtil.getHeader200(resourceSize).getBytes());

        // 读取内容输出
        long written = 0;// 已经读取的内容长度
        int byteSize = 1024; // 计划每次缓冲的长度
        byte[] bytes = new byte[byteSize];

        while (written < resourceSize) {
     
            if (written + byteSize > resourceSize) {
       // 说明剩余未读取大小不足一个1024长度,那就按真实长度处理
                byteSize = (int) (resourceSize - written);  // 剩余的文件内容长度
                bytes = new byte[byteSize];
            }

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

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

}

Bootstrap启动类

public class Bootstrap {
     

    private int port = 8080;

    /**
     * 完成Minicat 2.0版本
     * 需求:封装Request和Response对象,返回html静态资源文件
     */
    private void start() throws IOException {
     
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("=====>>>Minicat start on port:" + port);

        while (true) {
     
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();

            int read = inputStream.read();
            // 封装Request对象
            Request request = new Request(inputStream);

            // 封装Response对象
            Response response = new Response(outputStream);

            // 输出静态资源
            response.outPutHtml(request.getUrl());

            socket.close();
        }

    }

    public static void main(String[] args) {
     
        try {
     
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.start();
        } catch (IOException e) {
     
            e.printStackTrace();
        }
    }
}

HTML页面


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Minicat Indextitle>
head>
<body>
    <h1>Hello Minicat!h1>
body>
html>

测试
手写实现迷你版 Tomcat_第5张图片

三、V3.0版本

V3.0需求:可以请求动态资源(Servlet)

1、定义Servlet接口、抽象类HttpServlet

Servlet

public interface Servlet {
     

    void init() throws Exception;

    void destroy() throws Exception;

    void service(Request request, Response response) throws Exception;

}

HttpServlet

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".equalsIgnoreCase(request.getMethod())) {
     
            doGet(request, response);
        } else {
     
            doPost(request, response);
        }
    }
}

2、创建处理请求的Servlet实现类

public class TestServlet extends HttpServlet {
     

    @Override
    public void doGet(Request request, Response response) {
     
        String content = "

TestServlet get

"
; try { response.output((HttpProtocolUtil.getHeader200(content.getBytes().length) + content)); } catch (IOException e) { e.printStackTrace(); } } @Override public void doPost(Request request, Response response) { String content = "

TestServlet post

"
; try { response.output((HttpProtocolUtil.getHeader200(content.getBytes().length) + content)); } catch (IOException e) { e.printStackTrace(); } } @Override public void init() throws Exception { } @Override public void destroy() throws Exception { } }

3、在resources新建web.xml文件


<web-app>
    <servlet>
        <servlet-name>testServletservlet-name>
        <servlet-class>server.TestServletservlet-class>
    servlet>

    <servlet-mapping>
        <servlet-name>testServletservlet-name>
        <url-pattern>/testurl-pattern>
    servlet-mapping>
web-app>

4、新建xml解析器XPathParser,解析xml,获取所有servlet

/**
 * xml解析器
 */
public class XPathParser {
     

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

    public Map<String, HttpServlet> getServletMap() {
     
        return servletMap;
    }

    public void setServletMap(Map<String, HttpServlet> servletMap) {
     
        this.servletMap = servletMap;
    }

    /**
     * 加载解析相关的配置,web.xml
     */
    public void loadXml() {
     
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
        SAXReader saxReader = new SAXReader();
        try {
     
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> servletNodes = rootElement.selectNodes("//servlet");
            for (int i = 0; i < servletNodes.size(); i++) {
     
                Element element = servletNodes.get(i);
                // testServlet
                Element servletNameElement = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletNameElement.getStringValue();

                // server.TestServlet
                Element servletClassElement = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletClassElement.getStringValue();

                // 根据servlet-name的值找到url-pattern
                Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();

                servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());
            }

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

}

5、Bootstrap启动类

public class Bootstrap {
     

    private int port = 8080;

    /**
     * 完成Minicat 3.0版本
     * 需求:可以请求动态资源(Servlet)
     */
    private void start() throws Exception {
     
        // 加载解析相关的配置,web.xml
        XPathParser xPathParser = new XPathParser();
        // 解析xml
        xPathParser.loadXml();
        // 获取所有的servlet
        Map<String, HttpServlet> servletMap = xPathParser.getServletMap();

        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("=====>>>Minicat start on port:" + port);

        while (true) {
     
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();

            int read = inputStream.read();
            // 封装Request对象
            Request request = new Request(inputStream);

            // 封装Response对象
            Response response = new Response(outputStream);

            if (servletMap.get(request.getUrl()) == null) {
     
                // 输出静态资源
                response.outPutHtml(request.getUrl());
            } else {
     
                // 动态资源servlet请求
                HttpServlet httpServlet = servletMap.get(request.getUrl());
                httpServlet.service(request, response);
            }

            socket.close();
        }
    }

    public static void main(String[] args) {
     
        try {
     
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.start();
        } catch (Exception e) {
     
            e.printStackTrace();
        }
    }

}
  • 在start()方法中先从web.xml中加载解析相关的配置(主要解析所有Servlet,添加到Map集合中)
  • 在在根据请求的url从servletMap中查询是否有可以处理该请求的servlet,如果有可以处理该请求的servlet,则调用servlet进行处理请求。否则输出静态资源。

6、测试
手写实现迷你版 Tomcat_第6张图片
手写实现迷你版 Tomcat_第7张图片
7、存在问题

  假设访问http://localhost:8080/test这个请求路径,对应的TestServlet中的service()方法处理请求的时候比较长,那么当调用这个方法后,再通过浏览器访问静态资源的话需要等待,直到service()请求处理完成后才轮到静态资源的访问。因为在start()方法中是阻塞处理每一个请求的。

  解决方案:使用多线程处理请求。

四、使用多线程

1、创建请求处理器RequestProcessor

public class RequestProcessor extends Thread {
     

    private Socket socket;

    private Map<String, HttpServlet> servletMap;

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

    @Override
    public void run() {
     
        try {
     
            InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();

            int read = inputStream.read();
            // 封装Request对象和Response对象
            Request request = new Request(inputStream);
            Response response = new Response(outputStream);
            // 静态资源处理
            if (servletMap.get(request.getUrl()) == null) {
     
                // 输出静态资源
                response.outPutHtml(request.getUrl());
            } else {
     
                // 动态资源servlet请求
                HttpServlet httpServlet = servletMap.get(request.getUrl());
                httpServlet.service(request, response);
            }
            socket.close();
        } catch (Exception e) {
     
            e.printStackTrace();
        }
    }
}

2、创建线程池工具类 ThreadPoolUtil

public class ThreadPoolUtil {
     

    private ThreadPoolUtil() {
     
    }

    private static final ThreadPoolUtil INSTANCE = new ThreadPoolUtil();

    public static ThreadPoolUtil getInstance() {
     
        return INSTANCE;
    }

    // 核心线程数
    private int corePoolSize = 3;
    // 最大线程数
    private int maximumPoolSize = 10;
    // 心跳时间
    private long keepAliveTime = 100L;
    // 心跳时间单位
    private TimeUnit unit = TimeUnit.SECONDS;
    // 线程池队列
    private BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50);
    // 线程工厂
    private ThreadFactory threadFactory = Executors.defaultThreadFactory();
    // 拒绝策略
    private RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();

    // 自定义线程池
    private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            corePoolSize,
            maximumPoolSize,
            keepAliveTime,
            unit,
            workQueue,
            threadFactory,
            handler
    );

    /**
     * 获取线程池
     *
     * @return
     */
    public ThreadPoolExecutor getThreadPoolExecutor() {
     
        return threadPoolExecutor;
    }
}

3、Bootstrap启动类

public class Bootstrap {
     

    private int port = 8080;
    
    /**
     * 完成Minicat 3.0版本
     * 需求:可以请求动态资源(Servlet)
     */
    private void start() throws Exception {
     
        // 加载解析相关的配置,web.xml
        XPathParser xPathParser = new XPathParser();
        // 解析xml
        xPathParser.loadXml();
        // 获取所有的servlet
        Map<String, HttpServlet> servletMap = xPathParser.getServletMap();

        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("=====>>>Minicat start on port:" + port);

        // 获取线程池
        ThreadPoolExecutor threadPoolExecutor = ThreadPoolUtil.getInstance().getThreadPoolExecutor();

        while (true) {
     
            Socket socket = serverSocket.accept();
            // 使用多线程
            RequestProcessor requestProcessor = new RequestProcessor(socket, servletMap);
            // 执行任务
            threadPoolExecutor.execute(requestProcessor);
        }
    }

    public static void main(String[] args) {
     
        try {
     
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.start();
        } catch (Exception e) {
     
            e.printStackTrace();
        }
    }

}

你可能感兴趣的:(tomcat,tomcat)