创作缘由
平时使用 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