創作緣由
平時使用 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);@Overridepublic 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 {//... 省略基礎屬性@Overridepublic void register(String url, Filter filter) {logger.info("[MiniCat] register Filter, url={}, Filter={}", url, filter.getClass().getName());filterMap.put(url, filter);}@Overridepublic Filter getFilter(String url) {return filterMap.get(url);}@Overridepublic 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. 處理 filterfinal 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();//beforeList<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