<repo_name>zhi-lu/MasterLearning<file_sep>/src/com/luzhi/miniTomcat/watcher/WarFileWatcher.java
package com.luzhi.miniTomcat.watcher;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.watch.WatchMonitor;
import cn.hutool.core.io.watch.WatchUtil;
import cn.hutool.core.io.watch.Watcher;
import com.luzhi.miniTomcat.catalina.Host;
import com.luzhi.miniTomcat.util.ConstantTomcat;

import java.io.File;
import java.nio.file.Path;
import java.nio.file.WatchEvent;

import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;

/**
 * @author apple
 * @version jdk1.8
 * // TODO : 2021/7/2
 * 监控webapps下的文件,如果存在新创建的.war文件.则由{@link com.luzhi.miniTomcat.catalina.Host#loadWar(File)}方法进行处理.
 */
@SuppressWarnings("unused")
public class WarFileWatcher {

    /**
     * 创建监听器
     *
     * @see #watchMonitor
     */
    private final WatchMonitor watchMonitor;

    /**
     * war文件的扩展名:
     *
     * @see #WAR_EXTENSION
     */
    private static final String WAR_EXTENSION = ".war";

    public WarFileWatcher(Host host) {
        this.watchMonitor = WatchUtil.createAll(ConstantTomcat.WEBAPPS_FOLDER, 1, new Watcher() {
            private void dealWith(WatchEvent<?> event, Path currentPath) {
                synchronized (WarFileWatcher.class) {
                    String fileName = event.context().toString();
                    if (fileName.toLowerCase().endsWith(WAR_EXTENSION) && ENTRY_CREATE.equals(event.kind())) {
                        File warFile = FileUtil.file(ConstantTomcat.WEBAPPS_FOLDER, fileName);
                        host.loadWar(warFile);
                    }
                }

            }

            @Override
            public void onCreate(WatchEvent<?> event, Path currentPath) {
                dealWith(event, currentPath);
            }

            @Override
            public void onModify(WatchEvent<?> event, Path currentPath) {
                dealWith(event, currentPath);
            }

            @Override
            public void onDelete(WatchEvent<?> event, Path currentPath) {
                dealWith(event, currentPath);
            }

            @Override
            public void onOverflow(WatchEvent<?> event, Path currentPath) {
                dealWith(event, currentPath);
            }
        });
    }

    public void start() {
        watchMonitor.start();
    }

    public void stop() {
        watchMonitor.interrupt();
    }
}
<file_sep>/startup.sh
#!/usr/bin/env zsh
# shellcheck disable=SC2164
# shellcheck disable=SC2035
# shellcheck disable=SC2103
# 详细参数解释请看站长.
hello="你好吖,[]~（￣▽￣）~*.正在开启tomcat,<（￣︶￣）>."
echo "${hello}"
rm -rf bootstrap.jar
jar cvf0 bootstrap.jar -C out/production/MasterLearning com/luzhi/miniTomcat/Bootstrap.class -C out/production/MasterLearning com/luzhi/miniTomcat/classloader/CommonClassLoader.class
# 删除 lib 目录下的miniTomcat.jar
rm -rf lib/miniTomcat.jar
cd out
cd production
cd MasterLearning
jar cvf0 ../../../lib/miniTomcat.jar *
cd ..
cd ..
cd ..
java -cp bootstrap.jar com/luzhi/miniTomcat/Bootstrap<file_sep>/src/com/luzhi/miniTomcat/pratice/DemoThread.java
package com.luzhi.miniTomcat.pratice;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author apple
 * @version jdk1.8
 * // TODO : 2021/5/21
 * 对多线程的探索,关于synchronized的四种修饰方式
 * 在Java中 synchronized:
 * 对于接口方法不能使用synchronized进行修饰,构造方法不可以使用synchronized进行修饰,但它的synchronized代码块可以进行同步.
 * 《1》对于synchronized(this) 一个线程访问一个对象的同步代码块时,会使其他访问该对象的代码块线程出现阻塞状态。
 * 《2》对方法进行修饰,一个线程访问一个对象被synchronized修饰的方法时,会使其他访问该对象synchronized的方法线程进行堵塞.
 */
@SuppressWarnings("AlibabaAvoidManuallyCreateThread")
public class DemoThread {

    public static void main(String[] args) throws InterruptedException {

        runSyncThread();
        // 睡上2秒.便于查看.
        Thread.sleep(2000);
        runSynThread();
        Thread.sleep(2000);
        Child child = new Child();
        child.methods();
        Thread.sleep(2000);
        runSynchroThread();
        Thread.sleep(2000);
        runSynchronizedThread();
    }

    private static void runSyncThread() {
        SyncThread syncThread = new SyncThread();
        // 创建 A 线程.
        Thread thread = new Thread(syncThread, "SynchronizeOne");
        // 创建 B 线程.
        Thread thread1 = new Thread(syncThread, "SynchronizedTwo");
        thread.start();
        thread1.start();
    }

    private static void runSynThread() {

        SynThread synThread = new SynThread();
        // 创建 A 线程.
        Thread thread = new Thread(synThread, "SynThread-One");
        // 创建 B 线程.
        Thread thread1 = new Thread(synThread, "SynThread-Two");
        thread.start();
        thread1.start();
    }

    private static void runSynchroThread() {
        SynchroThread synchroThread = new SynchroThread();
        // 创建 A 线程.
        Thread thread = new Thread(synchroThread, "SynchroThread-One");
        // 创建 B 线程.
        Thread thread1 = new Thread(synchroThread, "SynchroThread-Two");
        thread.start();
        thread1.start();
    }

    private static void runSynchronizedThread() {
        SynchronizedThread synchronizedThread = new SynchronizedThread();
        // 创建 A 线程.
        Thread thread = new Thread(synchronizedThread, "synchronizedThread-One");
        // 创建 B 线程.
        Thread thread1 = new Thread(synchronizedThread, "synchronizedThread-Two");
        thread.start();
        thread1.start();
    }
}

@SuppressWarnings("unused")
class SyncThread implements Runnable {

    private static volatile int count;
    private static final int NUMBER = 5;

    public SyncThread() {
        count = 0;
    }

    /**
     * @see #run()
     * 对run进行重新.使用synchronized(this)
     */
    @Override
    public void run() {
        synchronized (this) {
            for (int i = 0; i < NUMBER; i++) {
                try {
                    System.out.println("打印当前线程名:" + Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException exception) {
                    exception.printStackTrace();
                }
            }
        }
    }

    public static int getCount() {
        return count;
    }
}

@SuppressWarnings("unused")
class SynThread implements Runnable {

    private static volatile int count;
    private static final int NUMBER = 5;

    public SynThread() {
        count = 0;
    }

    @Override
    public synchronized void run() {
        for (int i = 0; i < NUMBER; i++) {
            // 不使用 synchronized(this)
            try {
                System.out.println("打印线程名:" + Thread.currentThread().getName() + ":" + (count++));
                Thread.sleep(100);
            } catch (InterruptedException exception) {
                exception.printStackTrace();
            }
        }
    }

    public static int getCount() {
        return count;
    }
}


/**
 * {@link Parent} 对synchronized方法进行修饰和继承.
 */
class Parent {
    public synchronized void methods() {
        System.out.println("Hello,World,类对象为:" + this.getClass().getName());
    }
}

class Child extends Parent {

    /**
     * @see #methods()
     * 第一种重新方式.
     */
    @Override
    public synchronized void methods() {
        // 打印的是子类的对象名
        super.methods();
    }
}

/**
 * {@link SynchroThread} 修饰静态方法
 */
class SynchroThread implements Runnable {

    private static final int NUMBER = 6;
    private static AtomicInteger atomicInteger;

    public SynchroThread() {
        atomicInteger = new AtomicInteger(-1);
    }

    public static synchronized void methods() {
        for (int i = 0; i < NUMBER; i++) {
            try {
                System.out.println("打印线程名(原子性操作):" + Thread.currentThread().getName() + ":" + (atomicInteger.incrementAndGet()));
                Thread.sleep(100);
            } catch (InterruptedException exception) {
                exception.printStackTrace();
            }
        }
    }

    @Override
    public synchronized void run() {
        methods();
    }
}

/**
 * {@link SynchronizedThread} 修饰类
 */
class SynchronizedThread implements Runnable {
    private static int count;
    private static final int NUMBER = 6;

    public SynchronizedThread() {
        count = 0;
    }

    @Override
    public void run() {
        synchronized (SynchronizedThread.class) {
            for (int i = 0; i < NUMBER; i++) {
                try {
                    System.out.println("打印线程名:" + Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException exception) {
                    exception.printStackTrace();
                }
            }
        }
    }
}<file_sep>/src/com/luzhi/miniTomcat/test/TomcatTest.java
package com.luzhi.miniTomcat.test;


import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.NetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import com.luzhi.miniTomcat.util.MiniBrowser;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

/**
 * @author apple
 * @version jdk1.8
 * // TODO : 2021/5/11
 * 为自动测试类
 */
public class TomcatTest {

    /**
     * @see #PORT
     * <p>
     * 测试端口
     */
    private static final int PORT = 9091;

    /**
     * @see #HOST
     * <p>
     * 本地主机地址
     */
    private static final String HOST = "127.0.0.1";

    /**
     * @see #POOL_SIZE
     * <p>
     * 测试的线程数为3
     */
    private static final int POOL_SIZE = 3;

    /**
     * @see #beforeClass()
     * 该类为测试类,所有的测试之前Jvm执行该方法一次并使用{@link BeforeClass}注解,注解的方法必须为 public static void 所修饰
     * 它和{@link org.junit.Before} 的区别是每个测试都执行一遍. 注解的方法必须为 public static void 所修饰
     */
    @BeforeClass
    public static void beforeClass() {
        if (NetUtil.isUsableLocalPort(PORT)) {
            System.err.println("miniTomcat服务还没有启动,请检查端口是否启动该服务.谢谢");
            // 终止当前的Java虚拟机,非零表示为异常终止.
            System.exit(1);
        } else {
            System.out.println("miniTomcat服务已经启动啦, 下面进行单元测试哦!");

        }
    }

    /**
     * @see #testTomcat()
     * 对字符文本进行操作
     */
    @Test
    public void testTomcat() {
        String content = getContentString("/");
        Assert.assertEquals(content, "<h1 align='center' style='color:pink;'> Hello, I't is new simplify tomcat</h1>");
    }

    /**
     * @see #testHtml()
     * 对h5进行文本进行操作
     */
    @Test
    public void testHtml() {
        String content = getContentString("/login.html");
        Assert.assertEquals(content, "<h1 align='center' style='color:pink;'> Hello, I't is new simplify tomcat</h1>");
    }

    /**
     * @see #textHtmlCrossXml()
     * 对多应用配置(使用xml)进行配置进行测试
     */
    @Test
    public void textHtmlCrossXml() {
        String content = getContentString("/resource/manyConf.html");
        Assert.assertEquals(content, "<h1 style='color:pink; align-content: center'> Hello, I't is new simplify tomcat</h1>");
    }

    @Test
    public void testServletText() {
        String content = getContentString("/hello");
        Assert.assertEquals(content, "<h1 align=center style='color:pink'>Hello, It's really easy servlet</h1>");
    }

    /**
     * @see #testOtherHtml()
     * 对webapps的多应用文件进行测试
     */
    @Test
    public void testOtherHtml() {
        String content = getContentString("/a/start.html");
        Assert.assertEquals(content, "<h1 style='color:pink; align-content: center'> Hello, I't is new simplify tomcat</h1>");
    }

    /**
     * @see #testPage404()
     * 对"404"页面进行测试
     */
    @Test
    public void testPage404() {
        String response = getHttpString("/not_exist.html");
        containAssert(response, "HTTP/1.1 404 NOT FOUND");
    }

    /**
     * @see #testPage500()
     * 对"500"页面进行测试.
     */
    @Test
    public void testPage500() {
        String response = getHttpString("/500.html");
        containAssert(response, "HTTP/1.1 500 Internal Server Error");
    }

    @Test
    public void testJ2eeHello() {
        String content = getContentString("/j2ee/hello");
        Assert.assertEquals(content, "<h1 align=center style='color:pink'>Hello, It's really easy servlet</h1>");
    }

    @Test
    public void testJavaWebHello() {
        String content = getContentString("/javaweb/hello");
        String contentTest = getContentString("/javaweb/hello");
        Assert.assertEquals(content, contentTest);
    }

    /**
     * @see #testLoginHtml()
     * 创建3个线程对页面login.html进行访问.由于BootStrap现在是多线程处理.
     */

    @Test
    public void testLoginHtml() throws InterruptedException {
        // 创建倒数锁存器并初始化为3
        CountDownLatch countDownLatch = new CountDownLatch(3);
        // 使用计数器.统计下面线程运行的时间。默认初始 (isNano 为 false).
        TimeInterval timeInterval = DateUtil.timer();
        for (int i = 0; i < POOL_SIZE; i++) {
            //noinspection AlibabaAvoidManuallyCreateThread
            new Thread(() -> {
                getContentString("/login.html");
                // 将倒数锁存器初始化倒数逐级递减.
                countDownLatch.countDown();
            }, "Thread" + i).start();
        }
        // 将当前线程进行挂起.直到倒数锁存器的倒数递减为零.释放当前挂起的线程.
        countDownLatch.await();
        // 统计时间,从开始到当前所用的时间.默认为毫秒.
        long duration = timeInterval.intervalMs();
        // 因为是线程池进行处理.自然 duration 小于3000毫秒.
        Assert.assertTrue(duration < 3000);
    }

    /**
     * @see #testJpgFile() 对图片文件读取进行测试
     * 测试手段,比较文件长度大小.
     */
    @Test
    public void testJpgFile() {
        byte[] bytes = getContentBytes("/lastPic.jpg");
        final int fileLength = 505797;
        Assert.assertEquals(fileLength, bytes.length);
    }

    /**
     * @see #testPdfFile() 对pdf文件进行读取测试
     * 测试手段,上同.
     */
    @Test
    public void testPdfFile() {
        byte[] bytes = getContentBytes("/healthBook.pdf");
        final int pdfFileLength = 262524;
        Assert.assertEquals(pdfFileLength, bytes.length);
    }

    @Test
    public void testGetParam() {
        String uri = "/javaweb/param";
        //noinspection HttpUrlsUsage
        String url = StrUtil.format("http://{}:{}{}", HOST, PORT, uri);
        //noinspection MismatchedQueryAndUpdateOfCollection
        Map<String, Object> params = new HashMap<>(64);
        params.put("name", "鲁滍");
        // 测试GET方法
        String result = MiniBrowser.getContentString(url, params, true);
        Assert.assertEquals(result, "get的name属性为:鲁滍");
    }

    @Test
    public void testPostParam() {
        String uri = "/javaweb/param";
        //noinspection HttpUrlsUsage
        String url = StrUtil.format("http://{}:{}{}", HOST, PORT, uri);
        //noinspection MismatchedQueryAndUpdateOfCollection
        Map<String, Object> params = new HashMap<>(64);
        params.put("name", "白上吹雪");
        // 对POST请求体处理
        String result = MiniBrowser.getContentString(url, params, false);
        Assert.assertEquals(result, "post的name属性为:白上吹雪");
    }

    @Test
    public void testHeader() {
        String result = getContentString("/javaweb/header");
        Assert.assertEquals(result, "mini browser / java1.8");
    }

    @Test
    public void testCookie() {
        String html = getHttpString("/javaweb/setCookie");
        containAssert(html, "Set-Cookie:name=luzhi;Expires=");
    }

    @Test
    public void testGetCookie() throws IOException {
        @SuppressWarnings("HttpUrlsUsage")
        String url = StrUtil.format("http://{}:{}{}", HOST, PORT, "/javaweb/getCookie");
        URL u = new URL(url);
        HttpURLConnection httpUrlConnection = (HttpURLConnection) u.openConnection();
        httpUrlConnection.setRequestProperty("Cookie", "name=luzhi");
        httpUrlConnection.connect();
        InputStream inputStream = httpUrlConnection.getInputStream();
        String html = IoUtil.read(inputStream, StandardCharsets.UTF_8);
        containAssert(html, "name:luzhi");
    }

    @Test
    public void testSession() throws IOException {
        String jSessionId = getContentString("/javaweb/setSession");
        if (null != jSessionId) {
            jSessionId = jSessionId.trim();
        }
        //noinspection HttpUrlsUsage
        String url = StrUtil.format("http://{}:{}{}", HOST, PORT, "/javaweb/getSession");
        URL u = new URL(url);
        HttpURLConnection httpUrlConnection = (HttpURLConnection) u.openConnection();
        httpUrlConnection.setRequestProperty("Cookie", "JSESSIONID=" + jSessionId);
        httpUrlConnection.connect();
        InputStream inputStream = httpUrlConnection.getInputStream();
        String html = IoUtil.read(inputStream, StandardCharsets.UTF_8);
        containAssert(html, "luzhi");
    }

    @Test
    public void testGzip() {
        byte[] gzipContent = getContentBytes("/", true);
        byte[] unGzipContent = ZipUtil.unGzip(gzipContent);
        String content = new String(unGzipContent);
        containAssert(content, "<h1 align=\"center\" style=\"color: pink; align-content: center\">Welcome miniTomcat! miniTomcat already start.</h1>");
    }

    @Test
    public void testJavaWeb0Hello() {
        String html = getContentString("/javaweb0/hello.jsp");
        System.out.println(html);
        containAssert(html,"Hello");
    }

    @Test
    public void testJsp() {
        String html = getContentString("/javaweb/");
        Assert.assertEquals(html, "hello jsp@This is javaweb");
    }

    /**
     * @see #getContentString(String)
     * 该私有静态方法获取浏览器的内容,通过{@link MiniBrowser#getContentString(String)}方法进行获取.
     */
    private static String getContentString(String uri) {
        //noinspection HttpUrlsUsage
        String url = StrUtil.format("http://{}:{}{}", HOST, PORT, uri);
        return MiniBrowser.getContentString(url);
    }

    /**
     * @see #getHttpString(String)
     * 模拟资源不存在
     */
    private String getHttpString(String uri) {
        //noinspection HttpUrlsUsage
        String url = StrUtil.format("http://{}:{}{}", HOST, PORT, uri);
        return MiniBrowser.getHttpString(url);
    }

    /**
     * @see #containAssert(String, String)
     * 当资源不存在则设置断言.
     */
    private void containAssert(String html, String string) {
        boolean match = StrUtil.containsAny(html, string);
        Assert.assertTrue(match);
    }

    /**
     * @see #getContentString(String) 将路径资源文件读取为字节流.
     * 详情请看{@link #getContentBytes(String, boolean)}
     */
    private byte[] getContentBytes(@SuppressWarnings("SameParameterValue") String uri) {
        return getContentBytes(uri, false);
    }

    /**
     * @see #getContentBytes(String, boolean) 默认无压缩模式
     * 详细操作请看 {@link MiniBrowser#getContentString(String, boolean)};
     */
    private byte[] getContentBytes(String uri, @SuppressWarnings("SameParameterValue") boolean gzip) {
        //noinspection HttpUrlsUsage
        String url = StrUtil.format("http://{}:{}{}", HOST, PORT, uri);
        return MiniBrowser.getContentBytes(url, gzip);
    }
}
<file_sep>/src/com/luzhi/miniTomcat/catalina/Context.java
package com.luzhi.miniTomcat.catalina;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.LogFactory;
import com.luzhi.miniTomcat.classloader.WebAppClassLoader;
import com.luzhi.miniTomcat.exception.WebConfigDuplicatedException;
import com.luzhi.miniTomcat.http.ApplicationServlet;
import com.luzhi.miniTomcat.http.StandardServletConfig;
import com.luzhi.miniTomcat.util.ContextXmlUtil;
import com.luzhi.miniTomcat.watcher.ContextFileChangeWatcher;
import org.apache.jasper.JspC;
import org.apache.jasper.compiler.JspRuntimeContext;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import java.io.File;
import java.util.*;

/**
 * @author apple
 * @version jdk1.8
 * // TODO : 2021/5/21
 * 创建方法文件的地址和在系统文件的位置.
 */
@SuppressWarnings({"unused", "MismatchedQueryAndUpdateOfCollection"})
public class Context {

    /**
     * 匹配"/*
     *
     * @see #START_STAR
     */
    private static final String START_STAR = "/*";

    /**
     * 匹配"/*.jsp"文件
     *
     * @see #START_STAR_DOT
     */
    private static final String START_STAR_DOT = "/*.";

    /**
     * servletEvent初始化方法,对应的比较值
     *
     * @see #INIT_VALUE
     */
    private static final String INIT_VALUE = "init";

    /**
     * servletEvent销毁方法,对应的比较值.
     */
    private static final String DESTROYED_VALUE = "destroyed";
    /**
     * 获取映射的资源文件夹地址
     *
     * @see #path
     */
    private String path;

    /**
     * 获取资源地址的在系统的绝对路径.
     *
     * @see #docBase
     */
    private String docBase;

    /**
     * 创建host对象
     *
     * @see Host
     */
    private final Host host;

    /**
     * 是否进行重载
     *
     * @see #reloadable
     */
    private boolean reloadable;

    /**
     * 获取'*'/WEB-INF/xml文件
     *
     * @see #contextWebXmlFile
     */
    private final File contextWebXmlFile;

    /**
     * 设置自定义的ContextFile的监听器
     *
     * @see #contextFileChangeWatcher
     */
    private ContextFileChangeWatcher contextFileChangeWatcher;

    /**
     * 地址映射servlet类名
     *
     * @see #urlToServletClassName
     */
    private final Map<String, String> urlToServletClassName;

    /**
     * 地址映射servlet名
     *
     * @see #urlToServletName
     */
    private final Map<String, String> urlToServletName;

    /**
     * servlet类名映射servlet名.
     *
     * @see #servletClassNameToServletName
     */
    private final Map<String, String> servletClassNameToServletName;

    /**
     * servlet名映射servlet类名.
     *
     * @see #servletNameToServletClassName
     */
    private final Map<String, String> servletNameToServletClassName;

    /**
     * 存放初始化信息
     *
     * @see #servlet_className_init_params
     */
    private final Map<String, Map<String, String>> servlet_className_init_params;

    /**
     * 创建一个存放servlet对象池.
     *
     * @see #servletPool
     */
    private final Map<Class<?>, HttpServlet> servletPool;

    /**
     * 设置类的加载器为自定义的{@link WebAppClassLoader}加载器
     *
     * @see #webAppClassLoader
     */
    private final WebAppClassLoader webAppClassLoader;

    /**
     * 处理定义的Servlet的上下文.
     *
     * @see #servletContext
     */
    private final ServletContext servletContext;

    /**
     * 定义那些是需要自定义初始化的Servlet在Context启动之后.
     *
     * @see #loadOnStartupServletClassNames
     */
    private final List<String> loadOnStartupServletClassNames;

    /**
     * 创建webContext监听器列表.
     */
    private final List<ServletContextListener> servletContextListenerList;
    /**
     * url对应着筛选器的相应的类名
     *
     * @see #urlToFilterClassName
     */
    private final Map<String, List<String>> urlToFilterClassName;

    /**
     * url对应着筛选器
     *
     * @see #urlToFilterNames
     */
    private final Map<String, List<String>> urlToFilterNames;

    /**
     * 筛选器类对应的筛选器名
     *
     * @see #filterClassNameToFilterName
     */
    private final Map<String, String> filterClassNameToFilterName;

    /**
     * 筛选器名对应的筛选器
     *
     * @see #filterNameToFilterClassName
     */
    private final Map<String, String> filterNameToFilterClassName;

    /**
     * 创建筛选器池.
     *
     * @see #filterPool
     */
    private final Map<String, Filter> filterPool;

    /**
     * 筛选器类对应的相关的初始化的参数.
     *
     * @see #filterClassNameToInitParameters
     */
    private final Map<String, Map<String, String>> filterClassNameToInitParameters;


    /**
     * 详细解释通过{@code Thread.currentThread().getContextClassLoader()}获取{@link com.luzhi.miniTomcat.Bootstrap}
     * 中的{@link com.luzhi.miniTomcat.classloader.CommonClassLoader}公共类加载器.根据tomcat将此公共加载器设置为{@link WebAppClassLoader}
     * 的父类加载器。由它获取类和资源.
     *
     * @see #Context(String, String, Host, boolean)
     */
    public Context(String path, String docBase, Host host, boolean reloadable) {
        this.path = path;
        this.docBase = docBase;
        this.host = host;
        this.reloadable = reloadable;
        this.contextWebXmlFile = new File(docBase, ContextXmlUtil.getWatchedResource());
        this.loadOnStartupServletClassNames = new ArrayList<>();
        this.servletContextListenerList = new ArrayList<>();
        this.urlToServletClassName = new HashMap<>(512);
        this.urlToServletName = new HashMap<>(512);
        this.urlToFilterClassName = new HashMap<>(512);
        this.urlToFilterNames = new HashMap<>(512);
        this.filterPool = new HashMap<>(512);
        this.filterNameToFilterClassName = new HashMap<>(512);
        this.filterClassNameToInitParameters = new HashMap<>(512);
        this.filterClassNameToFilterName = new HashMap<>(512);
        this.servletClassNameToServletName = new HashMap<>(512);
        this.servletNameToServletClassName = new HashMap<>(512);
        this.servletPool = new HashMap<>(512);
        this.servlet_className_init_params = new HashMap<>(512);


        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        this.webAppClassLoader = new WebAppClassLoader(this.docBase, classLoader);
        this.servletContext = new ApplicationServlet(this);
        deploy();
    }

    private void deploy() {

        TimeInterval timeInterval = DateUtil.timer();
        LogFactory.get().info("Deploying web application directory {}", this.getPath());
        parseLoadListeners();
        init();
        if (this.reloadable) {
            this.contextFileChangeWatcher = new ContextFileChangeWatcher(this);
            this.contextFileChangeWatcher.start();
        }
        JspC jspC = new JspC();
        // 进行JspRuntimeContext初始化。
        new JspRuntimeContext(servletContext, jspC);
        LogFactory.get().info("Deploying of application directory {} has finished in {}", this.getDocBase(), timeInterval.intervalMs());
    }

    private void init() {
        if (!contextWebXmlFile.exists()) {
            return;
        }
        try {
            checkDuplicated();
        } catch (WebConfigDuplicatedException exception) {
            System.out.println("打印异常原因:" + exception.getMessage());
            exception.printStackTrace();
            return;
        }
        String xml = FileUtil.readUtf8String(this.contextWebXmlFile);
        Document document = Jsoup.parse(xml);
        parseServletMapping(document);
        parseFilterMapping(document);
        parseServletInitName(document);
        parseFilterInitParameters(document);
        initFilter();
        parseLoadOnStartup(document);
        handleLoadOnStartup();
        fireEvent("init");
    }

    private void initFilter() {
        Set<String> filterClassNames = filterClassNameToFilterName.keySet();
        for (String filterClassName : filterClassNames) {
            try {
                Class<?> clazz = this.getWebAppClassLoader().loadClass(filterClassName);
                Map<String, String> initParameters = filterClassNameToInitParameters.get(filterClassName);
                String filterName = filterClassNameToFilterName.get(filterClassName);
                FilterConfig filterConfig = new StandardFilterConfig(servletContext, initParameters, filterName);
                @SuppressWarnings("SuspiciousMethodCalls") Filter filter = filterPool.get(clazz);
                if (null == filter) {
                    filter = (Filter) ReflectUtil.newInstance(clazz);
                    filter.init(filterConfig);
                    filterPool.put(filterClassName, filter);
                }
            } catch (Exception exception) {
                throw new RuntimeException("异常原因:" + exception);
            }
        }
    }

    /**
     * 具体的映射解析.
     *
     * @param document 需要解析web.xml
     */
    private void parseServletMapping(Document document) {
        // 地址对servletName的映射
        Elements elementsUrlToServletName = document.select("servlet-mapping url-pattern");
        for (Element elementOne : elementsUrlToServletName) {
            String urlPattern = elementOne.text();
            String servletName = elementOne.parent().select("servlet-name").first().text();
            urlToServletName.put(urlPattern, servletName);
        }
        // servlet-name 和 servlet-className 之间的相互映射
        Elements elementServletClassNameToServletName = document.select("servlet servlet-name");
        for (Element elementTwo : elementServletClassNameToServletName) {
            String servletName = elementTwo.text();
            String servletClassName = elementTwo.parent().select("servlet-class").first().text();
            servletNameToServletClassName.put(servletName, servletClassName);
            servletClassNameToServletName.put(servletClassName, servletName);
        }
        // url地址对servlet-className的映射.
        Set<String> urls = urlToServletName.keySet();
        for (String url : urls) {
            String servletName = urlToServletName.get(url);
            String servletClass = servletNameToServletClassName.get(servletName);
            urlToServletClassName.put(url, servletClass);
        }
    }

    @SuppressWarnings("DuplicatedCode")
    private void parseFilterInitParameters(Document document) {
        Elements elements = document.select("filter-class");
        for (Element element : elements) {
            String filterClassName = element.text();
            Elements elementsInitParam = elements.parents().select("init-param");
            if (elementsInitParam.isEmpty()) {
                continue;
            }
            Map<String, String> initParam = new HashMap<>(512);
            for (Element elementInitParam : elementsInitParam) {
                String name = elementInitParam.select("init-name").get(0).text();
                String value = elementInitParam.select("init-value").get(0).text();
                initParam.put(name, value);
            }
            filterClassNameToInitParameters.put(filterClassName, initParam);
        }
    }

    /**
     * 解析配置的筛选器相关的表
     *
     * @param document 获取需要解析的web.xml文件
     * @see #parseFilterMapping(Document)
     */
    public void parseFilterMapping(Document document) {
        // url对应相关定义的筛选器的名称。
        Elements elements = document.select("filter-mapping url-pattern");
        for (Element element : elements) {
            String urlName = element.text();
            String filterName = element.parent().select("filter-name").first().text();
            List<String> filterNames = urlToFilterNames.computeIfAbsent(urlName, k -> new ArrayList<>());
            filterNames.add(filterName);
        }
        // filterName 和 filterClassName 表的解析
        Elements elementsTwo = document.select("filter filter-name");
        for (Element element : elementsTwo) {
            String filterName = element.text();
            String filterClassName = element.parent().select("filter-class").first().text();
            filterNameToFilterClassName.put(filterName, filterClassName);
            filterClassNameToFilterName.put(filterClassName, filterName);
        }
        // url对应的筛选器类.
        Set<String> urls = urlToFilterNames.keySet();
        for (String url : urls) {
            List<String> filterNames = urlToFilterNames.computeIfAbsent(url, k -> new ArrayList<>());
            for (String filterName : filterNames) {
                String filterClassName = filterNameToFilterClassName.get(filterName);
                List<String> filterClassNames = urlToFilterClassName.computeIfAbsent(url, k -> new ArrayList<>());
                filterClassNames.add(filterClassName);
            }
        }
    }

    /**
     * 使用{@link #checkDuplicated(Document, String, String)} 具体检查和配置相关的参数.
     *
     * @see #checkDuplicated()
     */
    private void checkDuplicated() throws WebConfigDuplicatedException {
        String xml = FileUtil.readUtf8String(this.contextWebXmlFile);
        Document document = Jsoup.parse(xml);
        checkDuplicated(document, "servlet servlet-name", "在servlet中出现相同的servlet-name:{},请检查WEB-INF/web.xml.");
        checkDuplicated(document, "servlet servlet-class", "在servlet中出现相同的servlet-class:{},请检查WEB-INF/web.xml");
        checkDuplicated(document, "servlet-mapping url-pattern", "在servlet-mapping中出现相同的url-pattern:{},请检查WEB-INF/web.xml");
    }

    /**
     * 检查xml中是否出现出现同样的配置出现的异常为{@link WebConfigDuplicatedException}
     *
     * @param document 获取{@link Document} 将xml转化的对象。
     * @param mapping  在xml中的结点对象.
     * @param desc     对异常处理的描述.
     * @see #checkDuplicated(Document, String, String)
     */
    private void checkDuplicated(Document document, String mapping, String desc) throws WebConfigDuplicatedException {
        Elements elements = document.select(mapping);
        List<String> contentList = new ArrayList<>();
        for (Element element : elements) {
            contentList.add(element.text());
        }
        // 进行排序,查看是否出现相同的配置
        Collections.sort(contentList);
        for (int i = 0; i < contentList.size() - 1; i++) {
            String preContent = contentList.get(i);
            String lastContent = contentList.get(i + 1);
            if (preContent.equals(lastContent)) {
                throw new WebConfigDuplicatedException(StrUtil.format(desc, preContent));
            }
        }
    }

    public String getPath() {
        return path;
    }

    public String getDocBase() {
        return docBase;
    }

    public void setDocBase(String docBase) {
        this.docBase = docBase;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public WebAppClassLoader getWebAppClassLoader() {
        return webAppClassLoader;
    }

    public String getServletClassName(String uri) {
        return urlToServletClassName.get(uri);
    }

    public boolean isReloadable() {
        return reloadable;
    }

    public void setReloadable(boolean reloadable) {
        this.reloadable = reloadable;
    }

    /**
     * 添加监听器
     *
     * @param listener 监听器
     * @see #addListener(ServletContextListener)
     */
    public void addListener(ServletContextListener listener) {
        this.servletContextListenerList.add(listener);
    }

    /**
     * 停止并销毁
     *
     * @see #stop()
     */
    public void stop() {
        webAppClassLoader.stop();
        contextFileChangeWatcher.stop();
        destroyServlets();
        fireEvent("destroyed");
    }

    /**
     * 加载父类对象{@link Host}去重载{@code Context.this}。
     *
     * @see #reload()
     */
    public void reload() {
        host.reload(this);
    }

    /**
     * 获取servlet类名
     *
     * @param uri 通过uri进行获取。
     * @see #useUrlToServletClassName(String)
     */
    public String useUrlToServletClassName(String uri) {
        return urlToServletClassName.get(uri);
    }

    /**
     * 通过{@link #servletPool} 获取servlet对象,如果在哈希表中不存在,则利用{@link Class#newInstance()}构造一个servlet对象。
     *
     * @param clazz 获取类{@code Class<?> clazz} servlet对象.
     * @see #getServlet(Class)
     */
    public synchronized HttpServlet getServlet(Class<?> clazz) throws
            InstantiationException, IllegalAccessException, ServletException {
        HttpServlet servlet = servletPool.get(clazz);
        if (servlet == null) {
            servlet = (HttpServlet) clazz.newInstance();
            ServletContext servletContextOne = this.getServletContext();
            String clazzName = clazz.getName();
            String servletName = servletClassNameToServletName.get(clazzName);
            Map<String, String> initParameters = servlet_className_init_params.get(clazzName);
            StandardServletConfig standardServletConfig = new StandardServletConfig(servletContext, initParameters, servletName);
            servlet.init(standardServletConfig);
            servletPool.put(clazz, servlet);
        }
        return servlet;
    }

    /**
     * 将web.xml进行解析,获取servletClassName属性,初始化名和初始化值的属性。存储到{@link #servlet_className_init_params}中.
     *
     * @param document 获取{@link Document}对象
     * @see #parseServletInitName(Document)
     */
    @SuppressWarnings("DuplicatedCode")
    private void parseServletInitName(Document document) {
        Elements servletClassNameElements = document.select("servlet-class");
        for (Element servletClassNameElement : servletClassNameElements) {
            String servletClassName = servletClassNameElement.text();
            Elements initElements = servletClassNameElement.parent().select("init-param");
            if (initElements.isEmpty()) {
                continue;
            }
            Map<String, String> initParams = new HashMap<>(512);
            for (Element element : initElements) {
                String name = element.select("init-name").get(0).text();
                String value = element.select("init-value").get(0).text();
                initParams.put(name, value);
            }
            servlet_className_init_params.put(servletClassName, initParams);
        }
    }

    /**
     * 解析相关到web.xml文件.获取web中定义到监听器。
     *
     * @see #parseLoadListeners()
     */
    private void parseLoadListeners() {
        try {
            if (!contextWebXmlFile.exists()) {
                return;
            }
            String xml = FileUtil.readUtf8String(contextWebXmlFile);
            Document document = Jsoup.parse(xml);

            Elements elements = document.select("listener listener-class");
            for (Element element : elements) {
                String listenerClassName = element.text();

                Class<?> clazz = this.getWebAppClassLoader().loadClass(listenerClassName);
                ServletContextListener listener = (ServletContextListener) clazz.newInstance();
                addListener(listener);
            }
        } catch (ClassNotFoundException | IORuntimeException | InstantiationException | IllegalAccessException exception) {
            throw new RuntimeException("异常原因:");
        }
    }

    private void fireEvent(String type) {
        ServletContextEvent event = new ServletContextEvent(servletContext);
        for (ServletContextListener servletContextListener : servletContextListenerList) {
            if (INIT_VALUE.equals(type)) {
                servletContextListener.contextInitialized(event);
            } else if (DESTROYED_VALUE.equals(type)) {
                servletContextListener.contextDestroyed(event);
            } else {
                System.out.println("请输入正确的type值.");
            }
        }
    }

    /**
     * 此方法获取那些Servlet对象是需要进行"自启动"操作的.
     *
     * @param document 获取xml文件转化为{@link Document}的对象
     * @see #parseLoadOnStartup(Document)
     */
    private void parseLoadOnStartup(Document document) {
        Elements loadOnStartupElements = document.select("load-on-startup");
        for (Element loadOnStartupElement : loadOnStartupElements) {
            String loadOnStartupServletClassName = loadOnStartupElement.parent().select("servlet-class").text();
            loadOnStartupServletClassNames.add(loadOnStartupServletClassName);
        }
    }

    /**
     * 设置getter方法.
     *
     * @see #getServletContext()
     */
    public ServletContext getServletContext() {
        return servletContext;
    }

    /**
     * 准备这个方法删除所有的Servlet对象.
     *
     * @see #destroyServlets()
     */
    private void destroyServlets() {
        Collection<HttpServlet> servlets = servletPool.values();
        for (HttpServlet servlet : servlets) {
            servlet.destroy();
        }
    }

    /**
     * 对这些类进行自启动.
     *
     * @see #handleLoadOnStartup()
     */
    public void handleLoadOnStartup() {
        for (String loadOnStartupServletClassName : loadOnStartupServletClassNames) {
            try {
                Class<?> clazz = webAppClassLoader.loadClass(loadOnStartupServletClassName);
                getServlet(clazz);
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | ServletException e) {
                LogFactory.get().info("异常原因:" + e.getMessage());
                e.printStackTrace();
            }
        }
    }

    /**
     * 三种匹配模式
     *
     * @param uri 需要匹配的资源路径
     * @see #getMatchedFilters(String)
     */
    public List<Filter> getMatchedFilters(String uri) {
        List<Filter> filters = new ArrayList<>();
        Set<String> patterns = urlToFilterClassName.keySet();
        Set<String> matchedPatterns = new HashSet<>();
        Set<String> matchedFilterClassNames = new HashSet<>();
        for (String pattern : patterns) {
            if (match(pattern, uri)) {
                matchedPatterns.add(pattern);
            }
            List<String> filterClassName = urlToFilterClassName.get(pattern);
            matchedFilterClassNames.addAll(filterClassName);
        }
        for (String filterClassName : matchedFilterClassNames) {
            Filter filter = filterPool.get(filterClassName);
            filters.add(filter);
        }
        return filters;
    }

    /**
     * 匹配方式
     *
     * @param pattern 匹配的模式
     * @param uri     需要匹配的资源字段
     * @see #match(String, String)
     */
    private boolean match(String pattern, String uri) {
        // 完全匹配;
        if (StrUtil.equals(pattern, uri)) {
            return true;
        }
        if (StrUtil.equals(pattern, START_STAR)) {
            return true;
        }
        if (StrUtil.startWith(pattern, START_STAR_DOT)) {
            String patternExtension = StrUtil.subAfter(pattern, ".", false);
            String uriExtension = StrUtil.subAfter(pattern, ".", false);
            return patternExtension.equals(uriExtension);
        }
        return false;
    }
}
<file_sep>/src/com/luzhi/miniTomcat/util/WebXmlUtil.java
package com.luzhi.miniTomcat.util;

import cn.hutool.core.io.FileUtil;
import com.luzhi.miniTomcat.catalina.Context;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.util.HashMap;
import java.util.Map;
import java.io.File;


/**
 * @author apple
 * @version jdk1.8
 * // TODO : 2021/5/21
 * 生成对conf下的web.xml进行解析.
 */

public abstract class WebXmlUtil {

    /**
     * @see #MIME_MAPPING_TYPE
     * 此Map对象存放Extension(文件扩展名)和Mime-Type(浏览器所识别对类型)。
     */
    private static final Map<String, String> MIME_MAPPING_TYPE = new HashMap<>(1024);

    /**
     * @see #isInitMime
     * 是否进行初始化对于hashMap表来说。
     */
    private static volatile boolean isInitMime = false;

    /**
     * @see #getMimeName(String)
     * 使用 valotile 避免出现两次出现化哈希表{@link #MIME_MAPPING_TYPE};
     * 双重锁机制,保证线程中的数据的可见性和互斥性.
     */
    public static String getMimeName(String extensionName) {
        if (!isInitMime) {
            // 不允许其他线程对该类进行访问.直到在local memory执行完毕,把操作好的数据数据刷入main memory为止.
            synchronized (WebXmlUtil.class) {
                if (!isInitMime) {
                    initMimeMapping();
                    isInitMime = true;
                }
            }
        }
        String mineName = MIME_MAPPING_TYPE.get(extensionName);
        if (null == mineName) {
            return "text/html";
        }
        return mineName;
    }

    /**
     * @see #initMimeMapping()
     * 对{@link #MIME_MAPPING_TYPE} HashMap进行初始化.
     * 填充文件扩展名,和文件mime-type(被浏览器所识别.)
     */
    private static void initMimeMapping() {
        String xml = FileUtil.readUtf8String(ConstantTomcat.SERVER_WEB_XML);
        // 进行解析
        Document document = Jsoup.parse(xml);
        Elements elements = document.select("mime-mapping");
        for (Element element : elements) {
            String extensionName = element.select("extension").text();
            String mimeType = element.select("mime-type").text();
            MIME_MAPPING_TYPE.put(extensionName, mimeType);
        }

    }

    /**
     * @param context 获取一个{@link Context} 对象获取"欢迎文件"的地址
     *                对web.xml进行解析.获取欢迎文件。
     * @see #getWelcomeFile(Context)
     */
    public static String getWelcomeFile(Context context) {
        String webXml = FileUtil.readUtf8String(ConstantTomcat.SERVER_WEB_XML);
        // 进行解析
        Document document = Jsoup.parse(webXml);
        // 获取元素标签
        Elements elements = document.select("welcome-file");
        for (Element element : elements) {
            // 对解析的标签获取其中的文本.
            String welcomeFileName = element.text();
            // 根据地址和文件名,获取文件对象.
            File file = new File(context.getDocBase(), welcomeFileName);
            // 是否存在该文件.
            if (file.exists()) {
                return file.getName();
            }
        }
        return "index.html";
    }
}
<file_sep>/src/com/luzhi/miniTomcat/catalina/Connector.java
package com.luzhi.miniTomcat.catalina;

import cn.hutool.log.LogFactory;
import com.luzhi.miniTomcat.http.Request;
import com.luzhi.miniTomcat.http.Response;
import com.luzhi.miniTomcat.util.ThreadPoolUtil;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author apple
 * @version jdk1.8
 * // TODO : 2021/5/21
 * 生成对server.xml"中连接器解析".
 */
@SuppressWarnings("unused")
public class Connector implements Runnable {

    /**
     * 服务端口
     *
     * @see #port
     */
    private int port;

    /**
     * 获取Service对象.
     *
     * @see #service
     */
    private final Service service;

    /**
     * 创建compression属性值(是否进行压缩)
     *
     * @see #compression
     */
    private String compression;

    /**
     * 创建compressionMinSize(超过多少字节数进行压缩)
     *
     * @see #compressionMinSize
     */
    private int compressionMinSize;

    /**
     * 创建onCompressionUserAgents(那些浏览器不需要压缩)
     *
     * @see #onCompressionUserAgents
     */
    private String onCompressionUserAgents;

    /**
     * 创建compressionMimeType(那些文件需要进行压缩.)
     *
     * @see #compressibleMimeType
     */
    private String compressibleMimeType;

    @Override
    public void run() {
        try {
            ServerSocket serverSocket = new ServerSocket(port);
            //noinspection InfiniteLoopStatement
            while (true) {
                // 收到一个浏览器服务器的请求.
                Socket socket = serverSocket.accept();
                Runnable runnable = () -> {
                    try {
                        Request request = new Request(socket, Connector.this);
                        System.out.println("浏览器读取的信息为: \r\n" + request.getRequestString());
                        System.out.println("Uri信息为: \r\n" + request.getUri());
                        Response response = new Response();
                        HttpProcessor processor = new HttpProcessor();
                        processor.execute(socket, request, response);
                    } catch (IOException exception) {
                        // 日志打印输出异常.
                        LogFactory.get().error(exception);
                    }
                };
                // 把当前定义的 runnable 丢入定义的线程池中.
                ThreadPoolUtil.run(runnable);
            }
        } catch (Exception exception) {
            LogFactory.get().error(exception);
            System.out.println("异常的原因:" + exception.getMessage());
        }

    }

    public void init() {
        LogFactory.get().info("Init ProtocolHandler [http-bio-{}]", port);
    }

    @SuppressWarnings("AlibabaAvoidManuallyCreateThread")
    public void start() {
        LogFactory.get().info("Start ProtocolHandler [http-bio-{}]", port);
        new Thread(this).start();
    }

    public Connector(Service service) {
        this.service = service;
    }

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

    public Service getService() {
        return service;
    }

    public int getCompressionMinSize() {
        return compressionMinSize;
    }

    public int getPort() {
        return port;
    }

    public String getCompressibleMimeType() {
        return compressibleMimeType;
    }

    public String getCompression() {
        return compression;
    }

    public void setCompression(String compression) {
        this.compression = compression;
    }

    public String getOnCompressionUserAgents() {
        return onCompressionUserAgents;
    }

    public void setCompressibleMimeType(String compressibleMimeType) {
        this.compressibleMimeType = compressibleMimeType;
    }

    public void setCompressionMinSize(int compressionMinSize) {
        this.compressionMinSize = compressionMinSize;
    }

    public void setOnCompressionUserAgents(String onCompressionUserAgents) {
        this.onCompressionUserAgents = onCompressionUserAgents;
    }
}
<file_sep>/webapps/javaweb1/web/WEB-INF/classes/com/luzhi/test.js
// noinspection JSUnusedGlobalSymbols

/**
 * @author apple
 * @version javaScript11
 * @since 1.0
 * // TODO : 2021/5/21
 */
import(document)

/**
 * 设置cookie值
 * @see #setCookie
 * @param paramName 设置参数名
 * @param paramValue 设置参数值
 * @param exDays 设置cookie多少天过期
 */
function setCookie(paramName, paramValue, exDays) {
    let date = new Date();
    date.setTime(date.getTime() + (exDays * 24 * 60 * 60 * 1000));
    let expires = "expires=" + date.toUTCString();
    document.cookie = paramName + "=" + paramValue + ";" + expires;
}

/**
 * 获取cookie值
 * @see getCookie
 * @param paramName 获取cookie键值
 */
function getCookie(paramName) {
    let name = paramName + "=";
    let cookieSet = document.cookie.split(";");
    for (let i = 0; i < cookieSet.length; i++) {
        let result = cookieSet[i].trim();
        if (result.indexOf(name) === 0) {
            return result.substring(name.length, result.length);
        }
    }
}

/**
 * 检查cookie值
 * @see checkCookie
 * @param paramName 需要检查的cookie值
 */
function checkCookie(paramName) {
    let name = getCookie(paramName);
    if (name !== "" && name != null) {
        window.alert("cookie值存在" + name + "呀");
    } else {
        name = window.prompt("输出键值:", "");
        if (name !== "" && name != null) {
            setCookie(paramName, name, 1);
        }
    }
}<file_sep>/src/com/luzhi/miniTomcat/catalina/Engine.java
package com.luzhi.miniTomcat.catalina;

import com.luzhi.miniTomcat.util.ServerXmlUtil;

import java.util.List;

/**
 * @author apple
 * @version jdk1.8
 * // TODO : 2021/5/21
 * 对miniTomcat的内置对象Engine进行处理.
 */
@SuppressWarnings("unused")
public class Engine {

    private final String defaultName;
    private final List<Host> hosts;
    private final Service service;

    public Engine(Service service) {
        this.defaultName = ServerXmlUtil.getEngineName();
        this.hosts = ServerXmlUtil.getHosts(this);
        this.service = service;
        checkDefaultHost();

    }

    /**
     * 检查{@link #getDefaultHost()}返回是否为空。
     *
     * @throws RuntimeException 如果Host为空抛出为运行异常.
     * @see #checkDefaultHost()
     */
    private void checkDefaultHost() {
        if (null == getDefaultHost()) {
            throw new RuntimeException("the defaultHost: " + this.defaultName + " not exist");
        }

    }

    /**
     * 遍历Engine的Host标签是否存在defaultHost。
     * 理论上可以处理多个Host,但事实上只处理一个Host.本地主机.
     *
     * @see #getDefaultHost()
     */
    public Host getDefaultHost() {
        for (Host host : hosts) {
            if (this.defaultName.equals(host.getName())) {
                return host;
            }
        }
        return null;
    }

    public Service getService() {
        return service;
    }
}
<file_sep>/src/com/luzhi/miniTomcat/catalina/Server.java
package com.luzhi.miniTomcat.catalina;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.log.LogFactory;
import cn.hutool.system.SystemUtil;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author apple
 * @version jdk1.8
 * // TODO : 2021/5/21
 * Server是最外层的元素,代表服务器本身.
 */
public class Server {

    private final Service service;

    public Server() {
        this.service = new Service(this);
    }

    public void start() {
        TimeInterval timeInterval = DateUtil.timer();
        logJvm();
        init();
        LogFactory.get().info("服务器启动使用{}毫秒", timeInterval.intervalMs());
    }

    /**
     * 由Server(服务器)进行处理.
     *
     * @see #init() 对请求和访问进行初始化.
     */
    private void init() {
        service.init();
    }

    /**
     * 此方法用作设置相关的输出日志.
     * 在该方法中使用 {@link LinkedHashMap} 而不用 {@link java.util.HashMap} 是因为前者存放是由序的Hash键值
     * 后者为无序存放Hash的键值.
     *
     * @see #logJvm()
     */
    private static void logJvm() {
        // 定义存放相关信息的Map.(Server 版本, System 相关信息, Jvm的一些相关信息.)
        Map<String, String> mapInfo = new LinkedHashMap<>(512);
        mapInfo.put("Server version", "Luzhi miniTomcat/1.0.1");
        mapInfo.put("Server built", "2021-5-21, 21:31:22");
        mapInfo.put("Server name", "1.0.1");
        mapInfo.put("OS Name \t", SystemUtil.get("os.name"));
        mapInfo.put("OS Version", SystemUtil.get("os.version"));
        mapInfo.put("System Architecture", SystemUtil.get("os.arch"));
        mapInfo.put("Java Home", SystemUtil.get("java.home"));
        mapInfo.put("Jvm Version", SystemUtil.get("java.runtime.version"));
        mapInfo.put("Jvm Vendor", SystemUtil.get("java.vm.specification.vendor"));
        Set<String> keySet = mapInfo.keySet();
        for (String key : keySet) {
            // 创建日志工厂设置info类型日志.
            LogFactory.get().info(key + ":\t\t" + mapInfo.get(key));
        }
    }
}
<file_sep>/src/com/luzhi/miniTomcat/classloader/WebAppClassLoader.java
package com.luzhi.miniTomcat.classloader;

import cn.hutool.core.io.FileUtil;
import com.luzhi.miniTomcat.catalina.Context;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;

/**
 * @author apple
 * @version jdk1.8
 * // TODO : 2021/5/6
 * 生成web应用的类的加载器.
 */
public class WebAppClassLoader extends URLClassLoader {
    /**
     * 该构成方法创建一个Web应用的类加载器.
     *
     * @param docBase           扫描{@link Context#getDocBase()}获取classes和jar资源
     * @param commonClassLoader 获取父类加载器
     * @see #WebAppClassLoader(String, ClassLoader)
     */
    public WebAppClassLoader(String docBase, ClassLoader commonClassLoader) {
        super(new URL[]{}, commonClassLoader);

        try {
            // 扫描路径
            File webInfFolder = new File(docBase, "WEB-INF");
            File classesFolder = new File(webInfFolder, "classes");
            File jarFolder = new File(webInfFolder, "lib");
            URL url;
            // 将classes目录扫入URL中添加后缀"/",如此这样才会把WEB-INF下的classes当做目录来处理.不然的话会将资源当作(.jar)文件进行处理
            url = new URL("file:" + classesFolder.getAbsolutePath() + "/");
            this.addURL(url);
            // 遍历WEB-INF下的lib中的(.jar)文件.{@code FileUtil.loopFiles(jarFolder)}将结果打包成一个List
            List<File> jarLists = FileUtil.loopFiles(jarFolder);
            for (File file : jarLists) {
                url = new URL("file:" + file.getAbsolutePath());
                this.addURL(url);
            }
        } catch (Exception exception) {
            System.out.println("打印异常原因:" + exception.getMessage());
            exception.printStackTrace();
        }
    }

    /**
     * 关闭该加载器.
     *
     * @see #stop()
     */
    public void stop() {
        try {
            close();
        } catch (IOException exception) {
            System.out.println("打印异常原因:" + exception.getMessage());
            exception.printStackTrace();
        }
    }
}
<file_sep>/shutdown.sh
#!/usr/bin/env zsh
# 结束由 startup.sh 启动的tomcat进程
# shellcheck disable=SC2006
# shellcheck disable=SC2009
echo "正在关闭 tomcat (╮（╯▽╰）╭ )"
# 查找启动进程PID号
Tomcat_Id=$(ps -ef | grep java | grep -v "grep" | awk '{print $2}')
# 判断$Tomcat_Id不为空
if [ "$Tomcat_Id" ]; then
  for id in $Tomcat_Id; do
    # 结束进程
    kill -9 "$id"
    echo "有关Tomcat_Id进程${id}已经结束,谢谢."
  done
  echo "tomcat 关闭完成（＞﹏＜）"

else
  echo "tomcat 进程不存在.请稍后尝试.(（￣３￣）a)"
fi
<file_sep>/src/com/luzhi/miniTomcat/http/Response.java
package com.luzhi.miniTomcat.http;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
 * @author apple
 * @version jdk1.8
 * // TODO:2021/5/20
 * 生成miniTomcat的Response,同理Response需要实现{@link HttpServletResponse}
 */
public class Response extends BaseResponse {
    /**
     * @see #stringWriter
     * 字符流将其输出收集到缓存字符串中,将输出的头信息写入缓存字符串中.
     */
    private final StringWriter stringWriter;

    /**
     * @see #writer
     * 因为浏览器返回的是字符流,所以在Java中不使用{@link java.io.PrintStream},
     * 而使用 {@link PrintWriter},把数据字符流写入 stringWrite中
     */
    private final PrintWriter writer;

    /**
     * @see #contentType
     * 保存浏览器的头信息.文本类型为 "text/html".
     */
    private String contentType;

    /**
     * @see #redirectPath
     * 重定向路径.
     */
    private String redirectPath;

    /**
     * @see #bytes
     * 保存二进制文件
     */
    private byte[] bytes;

    /**
     * @see #status
     * 设置状态值.
     */
    private int status;

    /**
     * @see #cookies
     * 声明一个Cookie列表
     */
    private final List<Cookie> cookies;

    /**
     * @see #Response()
     * 初始化构造Response()方法对获取对回复的数据进行进行操作.
     */
    public Response() {
        this.stringWriter = new StringWriter();
        this.cookies = new ArrayList<>();
        this.writer = new PrintWriter(stringWriter);
        this.contentType = "text/html";
    }

    @Override
    public void setContentType(String contentType) {
        this.contentType = contentType;
    }

    @Override
    public String getContentType() {
        return contentType;
    }

    /**
     * @see #getBody()
     * 获取当前stringWrite对象,对缓存的字节流,转化为字节数组.
     */
    public byte[] getBody() {
        if (null == bytes) {
            String content = stringWriter.toString();
            bytes = content.getBytes(StandardCharsets.UTF_8);
        }
        return bytes;
    }

    public void setBytes(byte[] bytes) {
        this.bytes = bytes;
    }

    public String getRedirectPath() {
        return redirectPath;
    }

    @Override
    public PrintWriter getWriter() {
        return writer;
    }

    @Override
    public int getStatus() {
        return status;
    }

    @Override
    public void setStatus(int status) {
        this.status = status;
    }

    @Override
    public void sendRedirect(String redirect){
        this.redirectPath = redirect;
    }

    @Override
    public void addCookie(Cookie cookie) {
        cookies.add(cookie);
    }

    public List<Cookie> getCookies() {
        return cookies;
    }

    public String getCookiesHeader() {
        String pattern = "EEE, d MMM yyyy HH:mm:ss 'GMT'";
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern, Locale.ENGLISH);
        StringBuilder stringBuffer = new StringBuilder();
        for (Cookie cookie : getCookies()) {
            stringBuffer.append("\r\n");
            stringBuffer.append("Set-Cookie:");
            stringBuffer.append(cookie.getName()).append("=").append(cookie.getValue()).append(";");
            // cookie.getMaxAge()为-1意味着Cookie中Expires is forever 直到浏览器关闭.
            if (-1 != cookie.getMaxAge()) {
                stringBuffer.append("Expires=");
                Date date = new Date();
                Date expire = DateUtil.offset(date, DateField.SECOND, cookie.getMaxAge());
                stringBuffer.append(simpleDateFormat.format(expire)).append(";");
            }
            if (null != cookie.getPath()) {
                stringBuffer.append("Path=").append(cookie.getPath());
            }
        }
        return stringBuffer.toString();
    }
}
<file_sep>/src/com/luzhi/miniTomcat/http/StandardSession.java
package com.luzhi.miniTomcat.http;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * @author apple
 * @version jdk1.8
 * // TODO : 2021/6/16
 * 生成自定义标准化Session实现{@link HttpSession} 接口
 */
public class StandardSession implements HttpSession {

    /**
     * @see #attributeMap
     * <p>
     * 在session存放相关的信息
     */
    private final Map<String, Object> attributeMap;

    /**
     * @see #id
     * <p>
     * 创建session唯一标识符
     */
    private final String id;

    /**
     * @see #creationTime
     * <p>
     * session会话创建的时间
     */
    private final long creationTime;

    /**
     * @see #lastAccessedTime
     * <p>
     * 用户结束session最后访问的时间
     */
    private long lastAccessedTime;

    /**
     * @see #servletContext
     * <p>
     * 接受当前需要创建session对象的ServletContext
     */
    private final ServletContext servletContext;

    /**
     * @see #maxInactiveInterval
     * <p>
     * 最大非活动session持续的时间,一般为30分钟,如果不进行登录默认session失效.
     */
    private int maxInactiveInterval;

    public StandardSession(String jSessionId, ServletContext servletContext) {
        this.attributeMap = new HashMap<>(512);
        this.id = jSessionId;
        this.servletContext = servletContext;
        this.creationTime = System.currentTimeMillis();

    }

    @Override
    public long getCreationTime() {
        return this.creationTime;
    }

    @Override
    public String getId() {
        return this.id;
    }

    @Override
    public long getLastAccessedTime() {
        return this.lastAccessedTime;
    }

    @SuppressWarnings("unused")
    public void setLastAccessedTime(long lastAccessedTime) {
        this.lastAccessedTime = lastAccessedTime;
    }

    @Override
    public ServletContext getServletContext() {
        return this.servletContext;
    }

    @Override
    public void setMaxInactiveInterval(int interval) {
        this.maxInactiveInterval = interval;
    }

    @Override
    public int getMaxInactiveInterval() {
        return this.maxInactiveInterval;
    }

    @Override
    @Deprecated
    public HttpSessionContext getSessionContext() {
        return null;
    }

    @Override
    public Object getAttribute(String name) {
        return attributeMap.get(name);
    }

    @Override
    public Object getValue(String name) {
        return null;
    }

    @Override
    public Enumeration<String> getAttributeNames() {
        return Collections.enumeration(attributeMap.keySet());
    }

    @Override
    public String[] getValueNames() {
        return new String[0];
    }

    @Override
    public void setAttribute(String name, Object value) {
        attributeMap.put(name, value);
    }

    @Override
    public void putValue(String name, Object value) {

    }

    @Override
    public void removeAttribute(String name) {
        attributeMap.remove(name);
    }

    @Override
    public void removeValue(String name) {

    }

    @Override
    public void invalidate() {
        attributeMap.clear();
    }

    @Override
    public boolean isNew() {
        return creationTime == lastAccessedTime;
    }
}
<file_sep>/src/com/luzhi/miniTomcat/http/Request.java
package com.luzhi.miniTomcat.http;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.luzhi.miniTomcat.catalina.Connector;
import com.luzhi.miniTomcat.catalina.Context;
import com.luzhi.miniTomcat.catalina.Engine;
import com.luzhi.miniTomcat.catalina.Service;
import com.luzhi.miniTomcat.util.MiniBrowser;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author apple
 * @version jdk1.8
 * // TODO : 2021/5/11
 * 生成miniTomcat的Request,为了把Request参数传入Servlet中,需要实现 {@link HttpServletRequest}
 */
public class Request extends BaseRequest {
    /**
     * 获取对Http解析的字符串
     *
     * @see #requestString
     */
    private String requestString;

    /**
     * 创建Context对象.
     *
     * @see #context
     */
    private Context context;

    /**
     * 处理的uri请求.
     *
     * @see #uri
     */
    private String uri;

    /**
     * 返回请求的method。
     *
     * @see #method
     */
    private String method;

    /**
     * 准备查询参数.
     *
     * @see #queryString
     */
    private String queryString;

    /**
     * 当前的请求是否为转发
     *
     * @see #forwarded
     */
    private boolean forwarded;

    /**
     * 从Connector获取Service对象,再从Service标签中获取Engine对象,在从获取的Engine获取Host对象
     *
     * @see #connector
     */
    private final Connector connector;

    /**
     * 从启动器获取Socket(套接字).
     *
     * @see #socket
     */
    private final Socket socket;

    /**
     * 创建一个Session对象.
     *
     * @see #session
     */
    private HttpSession session;

    /**
     * 存放相关参数的Map
     *
     * @see #paramMaps
     */
    private final Map<String, String[]> paramMaps;

    /**
     * 存放请求的头信息
     *
     * @see #headerMaps
     */
    private final Map<String, String> headerMaps;

    /**
     * 存放信息,便于服务器传参数.
     *
     * @see #attributesMap
     */
    private final Map<String, Object> attributesMap;

    /**
     * 存放Cookie对象的数组
     *
     * @see #cookies
     */
    private Cookie[] cookies;

    /**
     * 用于解析指定'?'是否出现被截取对字符串中
     *
     * @see #SEARCH_CHAR
     */
    private static final char SEARCH_CHAR = '?';

    /**
     * 用于分割地址的字符串. ==== "/"
     *
     * @see #DIVISION_STRING
     */
    private static final String DIVISION_STRING = "/";

    /**
     * 处理 get 请求
     *
     * @see #GET
     */
    private static final String GET = "GET";

    /**
     * 处理 post 请求
     *
     * @see #POST
     */
    private static final String POST = "POST";

    /**
     * http协议默认端口为:80
     *
     * @see #HTTP_PORT
     */
    private static final int HTTP_PORT = 80;

    /**
     * https协议默认端口为:443
     *
     * @see #HTTPS_PORT
     */
    private static final int HTTPS_PORT = 443;

    /**
     * http协议
     *
     * @see #HTTP
     */
    private static final String HTTP = "http";

    /**
     * https协议.
     *
     * @see #HTTPS
     */
    private static final String HTTPS = "https";

    /**
     * JSESSIONID
     *
     * @see #JSESSIONID
     */
    private static final String JSESSIONID = "JSESSIONID";

    /**
     * 初始化构造函数,由{@link Request}直接对http请求和uri进行解析。
     *
     * @see #Request(Socket, Connector)
     */
    public Request(Socket socket, Connector connector) throws IOException {
        this.socket = socket;
        this.connector = connector;
        this.paramMaps = new HashMap<>(512);
        this.headerMaps = new HashMap<>(512);
        this.attributesMap = new HashMap<>(512);
        parseHttpRequest();
        // 判断解析的Http请求的"头"和请求内容不为空.
        if (StrUtil.isEmpty(this.requestString)) {
            return;
        }
        parseUri();
        parseMethod();
        parseContext();
        if (!DIVISION_STRING.equals(context.getPath())) {
            uri = StrUtil.removePrefix(uri, context.getPath());
            // 如果去除context.getPath前缀后返回为空字符串
            if (StrUtil.isEmpty(uri)) {
                // 将uri 设置为"/"
                uri = "/";
            }
        }
        parseParameters();
        parseHeaderMap();
        parseCookie();
    }

    /**
     * 解析请求中method(Get,Post),获取请求值,并且不从最后开始索引.
     *
     * @see #parseMethod()
     */
    private void parseMethod() {
        method = StrUtil.subBefore(requestString, " ", false);
    }

    /**
     * 该方法对Http请求进行解析.使用{@link MiniBrowser#readBytes(InputStream, boolean)}
     * 返回一个解析完成的byte[]数组
     *
     * @see #parseHttpRequest()
     */
    private void parseHttpRequest() throws IOException {
        InputStream inputStream = this.socket.getInputStream();
        byte[] bytes = MiniBrowser.readBytes(inputStream, false);
        // 推荐使用StandardCharsets.UTF_8,因为会自动抛出 "字符不支持异常"
        this.requestString = new String(bytes, StandardCharsets.UTF_8);
    }

    /**
     * 该方法对Http解析返回的"头"和请求内容进行解析.
     *
     * @see #parseUri()
     */
    private void parseUri() {
        String temp;
        // 对字符进行截取,获取被两个字符空格截取对部分。
        temp = StrUtil.subBetween(this.requestString, " ", " ");
        // 判断'?' 是否在已经截取对字符串中.
        if (!StrUtil.contains(temp, SEARCH_CHAR)) {
            // '？' 不在截取的字符串中.
            this.uri = temp;
            return;
        }
        // 如果'？' 在截取的字符串中,返回前面的部分.不查找最后的一个分割字符串.
        temp = StrUtil.subBefore(temp, '?', false);
        this.uri = temp;
    }

    /**
     * 在Request 中创建解析Context.
     *
     * @see #parseContext()
     */
    private void parseContext() {
        Service service = connector.getService();
        Engine engine = service.getEngine();
        context = engine.getDefaultHost().getContext(uri);
        if (null != context) {
            return;
        }
        // 截取字符串
        String path = StrUtil.subBetween(this.uri, "/", "/");
        if (path == null) {
            path = "/";
        } else {
            path = "/" + path;
        }
        // 通过截取的字符串获取Context对象.
        context = engine.getDefaultHost().getContext(path);
        if (context == null) {
            context = engine.getDefaultHost().getContext("/");
        }
    }

    private void parseParameters() {
        System.out.println("打印queryString为:" + this.queryString);
        if (GET.equals(this.getMethod())) {
            String url = StrUtil.subBetween(requestString, " ", " ");
            if (StrUtil.contains(url, SEARCH_CHAR)) {
                queryString = StrUtil.subAfter(url, SEARCH_CHAR, false);
            }
        }
        if (POST.equals(this.getMethod())) {
            queryString = StrUtil.subAfter(requestString, "\r\n\r\n", false);
        }
        if (null == queryString) {
            return;
        }
        queryString = URLUtil.decode(queryString);
        String[] parametersValues = queryString.split("&");
        for (String parameterValue : parametersValues) {
            String[] nameValues = parameterValue.split("=");
            String name = nameValues[0];
            String value = nameValues[1];
            String[] values = paramMaps.get(name);
            if (null == values) {
                values = new String[]{value};
            } else {
                values = ArrayUtil.append(values, value);
            }
            paramMaps.put(name, values);
        }
    }

    public void parseHeaderMap() {
        StringReader stringReader = new StringReader(requestString);
        List<String> list = new ArrayList<>();
        // 把数据从Reader读取Collection集合中
        IoUtil.readLines(stringReader, list);
        for (int i = 1; i < list.size(); i++) {
            String line = list.get(i);
            if (line.length() == 0) {
                break;
            }
            String[] speList = line.split(":");
            String headerName = speList[0].toLowerCase();
            String headerValue = speList[1];
            headerMaps.put(headerName, headerValue);
        }
    }

    private void parseCookie() {
        List<Cookie> cookieList = new ArrayList<>();
        String cookieStringList = headerMaps.get("cookie");
        if (null != cookieStringList) {
            // 根据";"分隔获取数组
            String[] pairs = StrUtil.split(cookieStringList, ";");
            for (String pair : pairs) {
                if (null == pair) {
                    continue;
                }
                // 将获得的每个Cookie(key-value)根据"="进行分隔.
                String[] speCookie = StrUtil.split(pair, "=");
                Cookie cookie = new Cookie(speCookie[0].trim(), speCookie[1].trim());
                cookieList.add(cookie);
            }
        }
        this.cookies = ArrayUtil.toArray(cookieList, Cookie.class);
    }

    /**
     * 通过Cookie对象获取JSEESIONID
     *
     * @see #getJSessionIdFormCookie()
     */
    @SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
    public String getJSessionIdFormCookie() {
        if (null == cookies) {
            return null;
        }
        for (Cookie cookie : cookies) {
            if (JSESSIONID.equals(cookie.getName())) {
                return cookie.getValue();
            }
        }
        return null;
    }

    public void setSession(HttpSession session) {
        this.session = session;
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

    public Socket getSocket() {
        return socket;
    }

    public void setForwarded(boolean forwarded) {
        this.forwarded = forwarded;
    }

    public Context getContext() {
        return context;
    }

    public String getUri() {
        return uri;
    }

    public String getRequestString() {
        return requestString;
    }

    public Connector getConnector() {
        return connector;
    }

    public boolean isForwarded() {
        return forwarded;
    }

    @Override
    public RequestDispatcher getRequestDispatcher(String uri) {
        return new ApplicationRequestDispatcher(uri);
    }

    @Override
    public HttpSession getSession() {
        return session;
    }

    @Override
    public Cookie[] getCookies() {
        return this.cookies;
    }

    @Override
    public String getParameter(String name) {
        String[] values = paramMaps.get(name);
        if (null != values && 0 != values.length) {
            return values[0];
        }
        return null;
    }

    @Override
    public void setAttribute(String name, Object object) {
        attributesMap.put(name, object);
    }

    @Override
    public void removeAttribute(String name) {
        attributesMap.remove(name);
    }

    @Override
    public Object getAttribute(String name) {
        return attributesMap.get(name);
    }

    @Override
    public Enumeration<String> getAttributeNames() {
        return Collections.enumeration(attributesMap.keySet());
    }

    @Override
    public HttpSession getSession(boolean b) {
        return super.getSession(b);
    }

    @Override
    public String getMethod() {
        return method;
    }

    @Override
    public ServletContext getServletContext() {
        return context.getServletContext();
    }

    @Override
    public String getRealPath(String name) {
        return context.getServletContext().getRealPath(name);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return this.paramMaps;
    }

    @Override
    public Enumeration<String> getParameterNames() {
        return Collections.enumeration(paramMaps.keySet());
    }

    @Override
    public String[] getParameterValues(String name) {
        return paramMaps.get(name);
    }

    @Override
    public Enumeration<String> getHeaderNames() {
        return Collections.enumeration(headerMaps.keySet());
    }

    @Override
    public String getHeader(String name) {
        if (null == name) {
            return null;
        }
        return headerMaps.get(name.toLowerCase());
    }

    @Override
    public int getIntHeader(String name) {
        // 将值转换为int值,转换失败默认为0
        return Convert.toInt(headerMaps.get(name), 0);
    }

    @Override
    public String getLocalAddr() {
        return socket.getLocalAddress().getHostAddress();
    }

    @Override
    public String getLocalName() {
        return socket.getLocalAddress().getHostName();
    }

    @Override
    public int getLocalPort() {
        return socket.getLocalPort();
    }

    @Override
    public String getProtocol() {
        return "HTTP:/1.1";
    }

    @Override
    public String getRemoteAddr() {
        InetSocketAddress inetSocketAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
        String temp = inetSocketAddress.getAddress().toString();
        return StrUtil.subAfter(temp, "/", false);
    }

    @Override
    public String getRemoteHost() {
        InetSocketAddress inetSocketAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
        return inetSocketAddress.getHostName();
    }

    @Override
    public String getScheme() {
        return "http";
    }

    @Override
    public String getServerName() {
        return getHeader("host").trim();
    }

    @Override
    public int getServerPort() {
        return getLocalPort();
    }

    @Override
    public String getContextPath() {
        String result = this.context.getPath();
        if (DIVISION_STRING.equals(result)) {
            return "";
        }
        return result;
    }

    @Override
    @SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
    public String getRequestURI() {
        return uri;
    }

    @Override
    @SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
    public StringBuffer getRequestURL() {
        StringBuffer url = new StringBuffer();
        String scheme = getScheme();
        int port = getServerPort();
        if (port < 0) {
            port = 80;
        }
        url.append(scheme);
        url.append("://");
        url.append(getServerName());
        boolean http = (scheme.equals(HTTP) && port != HTTP_PORT);
        boolean https = (scheme.equals(HTTPS) && port != HTTPS_PORT);
        if (http || https) {
            url.append(":");
            url.append(port);
        }
        url.append(getRequestURI());
        return url;
    }

    @Override
    public String getServletPath() {
        return uri;
    }

}

<file_sep>/src/com/luzhi/miniTomcat/servlets/JspServlet.java
package com.luzhi.miniTomcat.servlets;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.luzhi.miniTomcat.catalina.Context;
import com.luzhi.miniTomcat.classloader.JspClassLoader;
import com.luzhi.miniTomcat.http.Request;
import com.luzhi.miniTomcat.http.Response;
import com.luzhi.miniTomcat.util.ConstantTomcat;
import com.luzhi.miniTomcat.util.JspUtil;
import com.luzhi.miniTomcat.util.WebXmlUtil;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;

/**
 * @author apple
 * @version jdk1.8
 * // TODO : 2021/6/11
 * 创建该列对Jsp文件解析的Servlet;
 */
public class JspServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    /**
     * @see #STATUES_INIT
     * <p>
     * /初始化界面.
     */
    private static final String STATUES_INIT = "/";

    private static final JspServlet INSTANCE = new JspServlet();

    public static synchronized JspServlet getInstance() {
        return INSTANCE;
    }

    /**
     * @see #JspServlet()
     * 禁止任何人对其实例化.
     */
    private JspServlet() {
    }

    @Override
    public void service(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
        try {
            Request request = (Request) httpServletRequest;
            Response response = (Response) httpServletResponse;

            String uri = request.getRequestURI();
            if (STATUES_INIT.equals(uri)) {
                uri = WebXmlUtil.getWelcomeFile(request.getContext());
            }
            String fileName = StrUtil.removePrefix(uri, STATUES_INIT);
            File file = FileUtil.file(request.getRealPath(fileName));
            if (file.exists()) {
                Context context = request.getContext();
                String path = context.getPath();
                String subFolder;
                if (STATUES_INIT.equals(path)) {
                    subFolder = "_";
                } else {
                    subFolder = StrUtil.subAfter(path, '/', false);
                }

                String servletClassPath = JspUtil.getServletClassPath(uri, subFolder);
                File jspServletClassFile = new File(servletClassPath);
                if (!jspServletClassFile.exists()) {
                    JspUtil.compileJsp(context, file);
                } else if (file.lastModified() > jspServletClassFile.lastModified()) {
                    JspUtil.compileJsp(context, file);
                    JspClassLoader.invalidJspClassLoader(uri, context);
                }
                String extensionName = FileUtil.extName(file);
                String miniType = WebXmlUtil.getMimeName(extensionName);
                response.setContentType(miniType);
                JspClassLoader jspClassLoader = JspClassLoader.getJspClassLoader(uri, context);
                String jspServletClassName = JspUtil.getJspServletClassName(uri, subFolder);
                Class<?> jspServletClass = jspClassLoader.loadClass(jspServletClassName);
                HttpServlet servlet = context.getServlet(jspServletClass);
                servlet.service(request, response);
                response.setStatus(ConstantTomcat.CODE_200);
            } else {
                response.setStatus(ConstantTomcat.CODE_404);
            }

        } catch (Exception exception) {
            throw new RuntimeException(exception);
        }
    }
}
<file_sep>/src/com/luzhi/miniTomcat/catalina/Host.java
package com.luzhi.miniTomcat.catalina;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RuntimeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.LogFactory;
import com.luzhi.miniTomcat.util.ConstantTomcat;
import com.luzhi.miniTomcat.util.ServerXmlUtil;
import com.luzhi.miniTomcat.watcher.WarFileWatcher;

import java.io.File;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * @author apple
 * @version jdk1.8
 * // TODO : 2021/5/21
 * 对miniTomcat的内置对象的Host进行处理
 */
@SuppressWarnings("unused")
public class Host {

    private String name;
    private final Engine engine;
    private static final String ROOT = "ROOT";
    private static Map<String, Context> contextMap;

    public Host(String name, Engine engine) {
        this.engine = engine;
        this.name = name;
        contextMap = new LinkedHashMap<>(128);

        scanContextsOnServerXml();
        scanContextsOnWebAppsFolder();
        LogFactory.get().info("<====================== HOST ======================>");
        scanWarOnWebAppsFolder();
        LogFactory.get().info("<====================== HOST END ===================>");
        new WarFileWatcher(this).start();
    }

    /**
     * 遍历webapps文件夹下的文件夹和文件.如果是文件的话直接跳过,
     * 如果是文件夹交由{@link #loadContext(File)}方法进行处理.
     *
     * @see #scanContextsOnWebAppsFolder()
     */
    private void scanContextsOnWebAppsFolder() {
        File[] folders = ConstantTomcat.WEBAPPS_FOLDER.listFiles();
        // 设置断言因为在File类中listFiles()如果被遍历的文件夹下如果为空则该方法返回null.
        assert folders != null;
        for (File folder : folders) {
            if (!folder.isDirectory()) {
                continue;
            }
            loadContext(folder);
        }
    }

    /**
     * 加载{@link Context}对象
     *
     * @param folder 获取需要处理的文件夹.
     * @see #loadContext(File)
     */
    private void loadContext(File folder) {
        String path = folder.getName();
        if (ROOT.equals(path)) {
            path = "/";
        } else {
            path = "/" + path;
        }
        String docBase = folder.getAbsolutePath();
        Context context = new Context(path, docBase, this, true);
        // 将获取web下的资源路径和初始化的context对象写入Map中.
        contextMap.put(context.getPath(), context);
    }

    /**
     * 把文件加加载为{@link Context} 对象;
     *
     * @param folder 需要处理的文件
     * @see #load(File)
     */
    public void load(File folder) {
        String path = folder.getName();
        if (ROOT.equals(path)) {
            path = "/";
        } else {
            path = "/" + path;
        }
        String docBase = folder.getAbsolutePath();
        Context context = new Context(path, docBase, this, false);
        contextMap.put(context.getPath(), context);
    }

    /**
     * 把war文件进行解析为目录,并把文件加载为相关的Context
     *
     * @param warFile 获取相关的war文件
     * @see #loadWar(File)
     */
    public void loadWar(File warFile) {
        String fileName = warFile.getName();
        String folderName = StrUtil.subBefore(fileName, ".", true);
        // 是否存在相应的Context对象
        Context context = getContext("/" + folderName);
        if (null != context) {
            return;
        }
        // 是否存在相应的文件夹对象.
        File folder = new File(ConstantTomcat.WEBAPPS_FOLDER, folderName);
        if (folder.exists()) {
            return;
        }
        // 移动war文件,因为jar命令只支持解压当前目录下
        File tempWarFile = FileUtil.file(ConstantTomcat.WEBAPPS_FOLDER, folderName, fileName);
        File contextFolder = tempWarFile.getParentFile();
        //noinspection ResultOfMethodCallIgnored
        contextFolder.mkdir();
        FileUtil.copyFile(warFile, tempWarFile);
        // 进行解压
        String command = "jar xvf " + fileName;
        System.out.println(command);
        Process process = RuntimeUtil.exec(null, contextFolder, command);
        try {
            process.waitFor();
        } catch (InterruptedException exception) {
            System.out.println("打印异常原因:" + exception.getMessage());
        }
        // 解压后删除文件对象
        //noinspection ResultOfMethodCallIgnored
        tempWarFile.delete();
        // 创建新的Context;
        load(contextFolder);
    }

    /**
     * 多应用配置.将{@link ServerXmlUtil#contextList(Host)}解析的{@link Context}进行遍历
     * 和保存的ContextMap中.
     *
     * @see #scanContextsOnServerXml()
     */
    private void scanContextsOnServerXml() {
        List<Context> contexts = ServerXmlUtil.contextList(this);
        for (Context context : contexts) {
            contextMap.put(context.getPath(), context);
        }
    }

    /**
     * 扫描webapps下的文件,处理.war文件
     *
     * @see #scanWarOnWebAppsFolder()
     */
    private void scanWarOnWebAppsFolder() {
        File folder = FileUtil.file(ConstantTomcat.WEBAPPS_FOLDER);
        File[] files = folder.listFiles();
        assert files != null;
        LogFactory.get().info("<HELLO>" + Arrays.stream(files).count());
        for (File file : files) {
            if (file.getName().toLowerCase().endsWith(".war")) {
                LogFactory.get().info("文件名:" + file.getName());
                loadWar(file);
            }
        }
    }

    /**
     * 具体重载操作.
     *
     * @param context 获取子类对象{@code Context.this}
     * @see #reload(Context)
     */
    public void reload(Context context) {
        LogFactory.get().info("Reloading context with name [{}] has started", context.getPath());
        String path = context.getPath();
        String docBase = context.getDocBase();
        boolean reloadable = context.isReloadable();
        // stop
        context.stop();
        // remove
        contextMap.remove(path);
        // 重新设置对应关系.
        Context newContext = new Context(path, docBase, this, reloadable);
        contextMap.put(newContext.getPath(), newContext);
        LogFactory.get().info("Reloading Context with name [{}] has completed", context.getPath());
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Context getContext(String path) {
        return contextMap.get(path);
    }

    public Engine getEngine() {
        return engine;
    }
}
