從零手寫實現 tomcat-11-filter 過濾器

老马啸西风發表於2024-05-12

創作緣由

平時使用 tomcat 等 web 伺服器不可謂不多,但是一直一知半解。

於是想著自己實現一個簡單版本,學習一下 tomcat 的精髓。

系列教程

從零手寫實現 apache Tomcat-01-入門介紹

從零手寫實現 apache Tomcat-02-web.xml 入門詳細介紹

從零手寫實現 tomcat-03-基本的 socket 實現

從零手寫實現 tomcat-04-請求和響應的抽象

從零手寫實現 tomcat-05-servlet 處理支援

從零手寫實現 tomcat-06-servlet bio/thread/nio/netty 池化處理

從零手寫實現 tomcat-07-war 如何解析處理三方的 war 包?

從零手寫實現 tomcat-08-tomcat 如何與 springboot 整合?

從零手寫實現 tomcat-09-servlet 處理類

從零手寫實現 tomcat-10-static resource 靜態資原始檔

從零手寫實現 tomcat-11-filter 過濾器

從零手寫實現 tomcat-12-listener 監聽器

前言

還記得我們最初 web.xml 中的 filter 嗎?

<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
    <!-- Filter 配置 -->
    <filter>
        <filter-name>LoggingFilter</filter-name>
        <filter-class>com.github.houbb.minicat.support.filter.MyMiniCatLoggingHttpFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>LoggingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

他的作用是什麼?我們又該如何解析實現呢?

filter 是什麼?

在Tomcat中,Filter可以被看作是一個網路請求的“安檢員”。

就像你進火車站或機場前要經過安檢一樣,網路請求在到達它最終的目的地(比如一個Servlet)之前,也可以先經過一些Filter的處理。

Filter主要有以下幾個作用:

過濾請求:Filter可以檢查進入的請求,看看它是否滿足某些條件。如果不滿足,Filter可以拒絕這個請求,就像安檢員發現你攜帶了違禁品,就不讓你進站一樣。

修改請求:除了檢查請求,Filter還可以修改請求。比如,它可以新增一些請求頭,或者改變請求引數等。

過濾響應:同樣,Filter也可以檢查伺服器的響應,看看它是否滿足某些條件。如果不滿足,Filter可以修改這個響應。

修改響應:除了檢查響應,Filter也可以修改響應的內容。比如,它可以新增一些額外的資訊,或者改變響應的格式等。

tomcat 如何處理 filter 的?

客戶端(比如瀏覽器)傳送一個請求到Tomcat。

Tomcat的聯結器(Connector)接收到這個請求。

請求首先經過所有的Filter鏈。每個Filter都有機會檢查和修改這個請求。

一旦所有的Filter都處理完畢,請求就到達它的目標Servlet。

Servlet處理請求,並生成一個響應。

響應再次經過Filter鏈,每個Filter都有機會檢查和修改這個響應。

最後,響應被髮送回客戶端。

自己實現

介面定義

這裡就不定義了,直接複用 servlet 的標準 api

介面實現

簡單的實現

package com.github.houbb.minicat.support.filter;

import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpFilter;
import java.io.IOException;

/**
 * 
 * @since 0.6.0
 */
public class MyMiniCatLoggingHttpFilter extends HttpFilter {

    private static final Log logger = LogFactory.getLog(MyMiniCatLoggingHttpFilter.class);

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        logger.info("[MiniCat] MyMiniCatLoggingHttpFilter#doFilter req={}, resp={}", req, res);

        super.doFilter(req, res, chain);
    }

}

應用啟動解析

DefaultFilterManager

定義一個 filter 的管理類

package com.github.houbb.minicat.support.filter.manager;

import com.github.houbb.heaven.util.lang.StringUtil;
import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.minicat.exception.MiniCatException;
import com.github.houbb.minicat.support.servlet.manager.DefaultServletManager;

import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * filter 管理
 *
 * @since 0.6.0
 * @author 老馬嘯西風
 */
public class DefaultFilterManager implements IFilterManager {

    //... 省略基礎屬性

    @Override
    public void register(String url, Filter filter) {
        logger.info("[MiniCat] register Filter, url={}, Filter={}", url, filter.getClass().getName());

        filterMap.put(url, filter);
    }

    @Override
    public Filter getFilter(String url) {
        return filterMap.get(url);
    }

    @Override
    public List<Filter> getMatchFilters(String url) {
        List<Filter> resultList = new ArrayList<>();

        for(Map.Entry<String, Filter> entry : filterMap.entrySet()) {
            String urlPattern = entry.getKey();
            if(url.matches(urlPattern)) {
                resultList.add(entry.getValue());
            }
        }

        return resultList;
    }


}

register 的時機

以本地的 web.xml 解析為例

/**
 * 處理 web 檔案
 */
protected void processWebXml() {
    try {
        SAXReader reader = new SAXReader();
        Document document = reader.read(webXmlFile);
        Element root = document.getRootElement();

        // ...

        //2. 處理 filter
        final IFilterManager filterManager = this.miniCatContextConfig.getFilterManager();
        processWebFilter(root, filterManager);

        // ...
    } catch (Exception e) {
        throw new MiniCatException(e);
    }
}

解析對應的 web.xml 標籤內容,註冊對應資訊:

protected void handleFilterConfigMap(Map<String, String> filterClassNameMap, Map<String, String> urlPatternMap, final IFilterManager filterManager) {
    try {
        for (Map.Entry<String, String> urlPatternEntry : urlPatternMap.entrySet()) {
            String filterName = urlPatternEntry.getKey();
            String urlPattern = urlPatternEntry.getValue();
            String className = filterClassNameMap.get(filterName);
            if (StringUtil.isEmpty(className)) {
                throw new MiniCatException("className not found for filterName: " + filterName);
            }
            Class servletClazz = Class.forName(className);
            Filter httpServlet = (Filter) servletClazz.newInstance();
            // 構建
            String fullUrlPattern = buildFullUrlPattern(urlPattern);
            filterManager.register(fullUrlPattern, httpServlet);
        }
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        throw new MiniCatException(e);
    }
}

這樣就可以後面呼叫了。

呼叫的時機

我們什麼時候呼叫呢?

當然是一個請求地址過來了,看是否有匹配的 filter,然後進行處理。

@Override
public void dispatch(final IMiniCatRequest request,
                     final IMiniCatResponse response,
                     final MiniCatContextConfig config) {
    final IServletManager servletManager = config.getServletManager();
    // 判斷檔案是否存在
    String requestUrl = request.getUrl();
    //before
    List<Filter> filterList = config.getFilterManager().getMatchFilters(requestUrl);
    // 獲取請求分發
    final IRequestDispatcher requestDispatcher = getRequestDispatcher(requestUrl);
    // 請求前
    filterList.forEach(filter -> {
        try {
            filter.doFilter(request, response, null);
        } catch (IOException | ServletException e) {
            throw new RuntimeException(e);
        }
    });
    // 正式分發
    requestDispatcher.dispatch(request, response, config);
}

這樣,一個基礎的 filter 功能就實現了。

開源地址

 /\_/\  
( o.o ) 
 > ^ <

mini-cat 是簡易版本的 tomcat 實現。別稱【嗅虎】(心有猛虎,輕嗅薔薇。)

開源地址:https://github.com/houbb/minicat

相關文章