手写web server: 5-FilterChain

60 阅读2分钟

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

一、前言

Filter是Servlet规范中的一个重要组件。 它的作用是在HTTP请求到达Servlet之前进行预处理。它可以被一个或多个Filter按照一定的顺序组成一个处理链(FilterChain),用来处理一些公共逻辑。

二、目标

实现Filter功能,让web Server有实现拦截功能。

关注点:

1、Filter和Servlet一样是重要组件,所以也放在ServletContext中进行管理。

2、调用时将哪些Filter组装成当前请求的FilterChain。

3、filter执行完毕后,回归到目标servlet。

三、设计

1、因为Filter和Servlet同属三大重要组件,所以在组成上有很多相似性。 Servlet在ServletContext中有相关的3个成员:servletRegistrationsnameToServletsservletMappings 这里也同样为Filter增加3个成员:filterRegistrationsnameToFiltersfilterMappings

相互对比的功能也是类似,filterRegistrations主要完成对Filter的注册,nameToFilters映射Filter的name和Filter,filterMappings缓存urlPattern和Filter之间的映射关系。

2、针对请求处理环节 根据请求path在servletMappings匹配到目标servelet后,还需要根据path在filterMappings匹配出目标Fileer组成FilterChain。 filter执行的终点,还得回归到servlet上。这里可以将匹配出来的servlet和filter都整合到FilterChain上。

四、实现

1、uml类图如下:

ServletContext:

image.png

FilterChain:

image.png

2、Servlet中新增对Filter的初始化方法initFilters

    public void initFilters(List<Class<?>> filterClasses) {
        for (Class<?> c : filterClasses) {
            WebFilter wf = c.getAnnotation(WebFilter.class);
            if (wf != null) {
                logger.info("auto register @WebFilter: {}", c.getName());
                @SuppressWarnings("unchecked")
                Class<? extends Filter> clazz = (Class<? extends Filter>) c;
                // 将Filter注册到 filterRegistrations中
                FilterRegistration.Dynamic registration = this.addFilter(AnnoUtils.getFilterName(clazz), clazz);
                // 这里读取@WebFilter(urlPatterns = "/xxx")urlPatterns信息 
                // 并添加到registration的urlPatterns中去               
              registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, AnnoUtils.getFilterUrlPatterns(clazz));
                registration.setInitParameters(AnnoUtils.getFilterInitParams(clazz));
            }
        }

        // init filters:
        for (String name : this.filterRegistrations.keySet()) {
            var registration = this.filterRegistrations.get(name);
            try {
                registration.filter.init(registration.getFilterConfig());
                this.nameToFilters.put(name, registration.filter);
                // Filter的urlPattern增加映射关系
                for (String urlPattern : registration.getUrlPatternMappings()) {
                    this.filterMappings.add(new FilterMapping(urlPattern, registration.filter));
                }
                registration.initialized = true;
            } catch (ServletException e) {
                logger.error("init filter failed: " + name + " / " + registration.filter.getClass().getName(), e);
            }
        }
    }

3、修改请求处理逻辑process方法

新增逻辑:根据请求path匹配filter、组装FilterChain,然后调用chain.doFilter(request, response);

    public void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        String path = request.getRequestURI();
        // search servlet:
        Servlet servlet = null;
        for (ServletMapping mapping : this.servletMappings) {
            if (mapping.matches(path)) {
                servlet = mapping.servlet;
                break;
            }
        }
        if (servlet == null) {
            // 404 Not Found:
            PrintWriter pw = response.getWriter();
            pw.write("<h1>404 Not Found</h1><p>No mapping for URL: " + path + "</p>");
            pw.close();
            return;
        }
        // search filter:
        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 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;
        }
    }

FilterChain中重写的doFilter方法:

    @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);
        }
    }

total用于维护当前FilterChain中的filter总数。 index用于维护当前执行的filter。 如果当前filter都执行完了,则开始执行目标servlet。