手写web server: 8-实现ClassLoader Mini-WebServer上车

86 阅读2分钟

【声明】文章为本人学习时记录的笔记。 原课程地址:www.liaoxuefeng.com/wiki/154595…

一、前言

我们的WebApp通过Maven打包成war包以后,会将应用的.class文件和资源统一打包起来。 格式为:

image.png

这里有个问题,tomcat在启动时,tomcat服务器的类均可以被JVM的AppClassLoader加载。而tomcat中WebApp中的类如何加载到jvm中?AppClassLoader肯定是不行了,因为tomcat服务器的classpath和WebApp的并不是同一个,所以此时需要我们自己来编写ClassLoader,从而将WebApp中的.class文件加载到jvm中来。 WebApp中的.class文件存在于WEB-INF下的classes目录(WebApp的业务代码编译产物)和lib目录下(WebApp项目依赖的jar文件)。

通过我们自己的classLoader将WebApp中的class(主要是Servlet的三大组件)加载到jvm中后,注册到ServletContext中,并读取Server的配置,启动Server。就能完成一个Web Server的功能了。

二、目标

初步完成一个mini的Web Server服务器。

1、编写ClassLoader,来加载WebApp的class

2、server信息配置化

3、Connector完善(启用线程池)

4、ServletContext完善(完成WebApp中的Servlet三大组件的注册)

5、Connector的handler完善(设置线程的上下文类加载器为我们自己的ClassLoader)

三、设计

Mini WebServer的系统架构图:

image.png

WebServer启动流程:

image.png

服务处理请求流程:

image.png

四、实现

1、实现我们自己的ClassLoader,用来加载WebApp的class

    // 扫描WebApp的classPath
    void scanClassPath0(Consumer<Resource> handler, Path basePath, Path path) {
        try {
            Files.list(path).sorted().forEach(p -> {
                if (Files.isDirectory(p)) {
                    scanClassPath0(handler, basePath, p);
                } else if (Files.isRegularFile(p)) {
                    Path subPath = basePath.relativize(p);
                    handler.accept(new Resource(p, subPath.toString().replace('\\', '/')));
                }
            });
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }


    // 扫描WebApp依赖的jar包
    void scanJar0(Consumer<Resource> handler, Path jarPath) throws IOException {
        JarFile jarFile = new JarFile(jarPath.toFile());
        jarFile.stream().filter(entry -> !entry.isDirectory()).forEach(entry -> {
            String name = entry.getName();
            handler.accept(new Resource(jarPath, name));
        });
    }

2、创建Connector。初始化ServletContxt,设置Executor,启动web服务器。

    public HttpConnector(Config config, String webRoot, Executor executor, ClassLoader classLoader, List<Class<?>> autoScannedClasses) throws IOException {
        logger.info("starting http server at {}:{}...", config.server.host, config.server.port);
        this.config = config;
        this.classLoader = classLoader;

        // init servlet context:
        Thread.currentThread().setContextClassLoader(this.classLoader);
        ServletContextImpl ctx = new ServletContextImpl(classLoader, config, webRoot);
        ctx.initialize(autoScannedClasses);
        this.servletContext = ctx;
        Thread.currentThread().setContextClassLoader(null);

        // start http server:
        this.httpServer = HttpServer.create(new InetSocketAddress(config.server.host, config.server.port), config.server.backlog, "/", this);
        this.httpServer.setExecutor(executor);
        this.httpServer.start();
        logger.info("http server started at {}:{}...", config.server.host, config.server.port);
    }

3、ServletContext处理请求

    public void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        String path = request.getRequestURI();
        // 根据请求路径过滤目标Servlet
        Servlet servlet = null;
        for (ServletMapping mapping : this.servletMappings) {
            if (mapping.matches(path)) {
                servlet = mapping.servlet;
                break;
            }
        }
        if (servlet == null) {
            // 没有匹配到 则404
            PrintWriter pw = response.getWriter();
            pw.write("<h1>404 Not Found</h1><p>No mapping for URL: " + path + "</p>");
            pw.close();
            return;
        }
        // 过滤filter,组建filterchain
        List<Filter> enabledFilters = new ArrayList<>();
        for (FilterMapping mapping : this.filterMappings) {
            if (mapping.matches(path)) {
                enabledFilters.add(mapping.filter);
            }
        }
        Filter[] filters = enabledFilters.toArray(Filter[]::new);
        logger.atDebug().log("process {} by filter {}, servlet {}", path, Arrays.toString(filters), servlet);
        // filterChain不仅包含一组filter,还包含一个servlet,这样保证最后一个请求是servlet
        FilterChain chain = new FilterChainImpl(filters, servlet);
        try {
            chain.doFilter(request, response);
        } catch (ServletException e) {
            logger.error(e.getMessage(), e);
            throw new IOException(e);
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
            throw e;
        }
    }

4、filter执行完后,执行目标servlet

    @Override
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if (index < total) {
            int current = index;
            index++;
            filters[current].doFilter(request, response, this);
        } else {
            servlet.service(request, response);
        }
    }