从零手写实现 tomcat-09-servlet 处理类

种地  金牌会员 | 2024-5-19 18:33:45 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 935|帖子 935|积分 2805

创作缘由

寻常使用 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 中的 servlet 吗?
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <web-app>
  3.    
  4.     <servlet>
  5.         <servlet-name>my</servlet-name>
  6.         <servlet-class>com.github.houbb.minicat.support.servlet.MyMiniCatHttpServlet</servlet-class>
  7.     </servlet>
  8.     <servlet-mapping>
  9.         <servlet-name>my</servlet-name>
  10.         <url-pattern>/my</url-pattern>
  11.     </servlet-mapping>
  12. </web-app>
复制代码
servlet 是什么?我们又该如何解析实现呢?
servlet 是什么?

Servlet可以被看作是Tomcat中的一个“服务员”。
就像餐厅里的服务员负责接待顾客、接收点餐、上菜等使命一样,Servlet在Web服务器中负责处理网络请求,并返回响应结果。
Servlet的主要工作包罗:

  • 接收请求:当浏览器(客户端)发送一个HTTP请求到Tomcat时,这个请求会被映射到一个特定的Servlet。
  • 处理请求:Servlet会根据请求的类型(好比GET或POST)和内容,实验相应的处理逻辑。好比,假如请求是要表现一个网页,Servlet就会生成这个网页的内容。
  • 生成响应:处理完请求后,Servlet会生成一个HTTP响应,这个响应包含了客户端需要的信息,好比网页内容、图片、视频等。
  • 返回响应:最后,Servlet把生成的响应返回给客户端,客户端收到响应后,就可以展示网页或者进行其他操作。
servlet 处理流程


  • 客户端(好比浏览器)发送一个HTTP请求到Tomcat。
  • Tomcat的请求分发器(RequestDispatcher)会根据请求的URL,找到对应的Servlet。
  • Tomcat调用Servlet的service()方法,把请求交给Servlet处理。在service()方法内部,Servlet会根据HTTP请求的方法(GET、POST等)调用相应的处理方法。
  • Servlet处理请求,并生成响应。好比,假如是GET请求,Servlet大概会查询数据库,生成一个网页;假如是POST请求,Servlet大概会处理表单数据,实验一些业务逻辑。
  • Servlet把生成的响应返回给Tomcat。
  • Tomcat把响应发送回客户端。
通过使用Servlet,你可以灵活地处理各种HTTP请求,并生成相应的响应。
举个板栗

好的,让我们用餐厅的例子来比喻Tomcat中的Servlet:
想象一下,你走进一家餐厅,服务员会过来接待你。
服务员会问你需要什么服务,这就像是HTTP请求。在Tomcat中,Servlet就扮演了服务员的角色。

  • 接收点餐:当顾客(客户端)进入餐厅(访问网站),服务员(Servlet)会过来记载顾客的点餐(接收HTTP请求)。
  • 处理点餐:服务员会根据顾客的点餐内容(请求类型,如GET或POST),去厨房(后端逻辑)准备食品(处理请求)。
  • 上菜:食品准备好后,服务员会将食品(响应内容)端上桌(生成HTTP响应)。
  • 结账:顾客享用完毕后,服务员会拿来账单(请求竣事,返回响应),顾客结账离开。
Servlet通过这种方式,可以处理各种类型的点餐(请求),无论是简朴的查看菜单(静态页面请求),照旧复杂的定制菜品(复杂的业务逻辑请求)。
通过合理设计Servlet,餐厅(网站)可以提供丰富多样的服务(功能)。
自己实现

接口定义

这里就不定义了,直接复用 servlet 的尺度 api
抽象类

我们实现一个底子的抽象类:
  1. package com.github.houbb.minicat.support.servlet;
  2. import com.github.houbb.minicat.constant.HttpMethodType;
  3. import javax.servlet.ServletException;
  4. import javax.servlet.ServletRequest;
  5. import javax.servlet.ServletResponse;
  6. import javax.servlet.http.HttpServlet;
  7. import javax.servlet.http.HttpServletRequest;
  8. import javax.servlet.http.HttpServletResponse;
  9. import java.io.IOException;
  10. public abstract class AbstractMiniCatHttpServlet extends HttpServlet {
  11.     public abstract void doGet(HttpServletRequest request, HttpServletResponse response);
  12.     public abstract void doPost(HttpServletRequest request, HttpServletResponse response);
  13.     @Override
  14.     public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
  15.         HttpServletRequest httpServletRequest = (HttpServletRequest) req;
  16.         HttpServletResponse httpServletResponse = (HttpServletResponse) res;
  17.         if(HttpMethodType.GET.getCode().equalsIgnoreCase(httpServletRequest.getMethod())) {
  18.             this.doGet(httpServletRequest, httpServletResponse);
  19.             return;
  20.         }
  21.         this.doPost(httpServletRequest, httpServletResponse);
  22.     }
  23. }
复制代码
接口实现

简朴的实现
  1. package com.github.houbb.minicat.support.servlet;
  2. import com.github.houbb.log.integration.core.Log;
  3. import com.github.houbb.log.integration.core.LogFactory;
  4. import com.github.houbb.minicat.dto.IMiniCatResponse;
  5. import com.github.houbb.minicat.dto.MiniCatResponseBio;
  6. import com.github.houbb.minicat.util.InnerHttpUtil;
  7. import javax.servlet.http.HttpServletRequest;
  8. import javax.servlet.http.HttpServletResponse;
  9. /**
  10. * 仅用于测试
  11. *
  12. * @since 0.3.0
  13. * @author 老马啸西风
  14. */
  15. public class MyMiniCatHttpServlet extends AbstractMiniCatHttpServlet {
  16.     private static final Log logger = LogFactory.getLog(MyMiniCatHttpServlet.class);
  17.     // 方法实现
  18.     @Override
  19.     public void doGet(HttpServletRequest request, HttpServletResponse response) {
  20.         logger.info("MyMiniCatServlet-get");
  21.         // 模拟耗时
  22.         try {
  23.             Thread.sleep(5000);
  24.         } catch (InterruptedException e) {
  25.             e.printStackTrace();
  26.         }
  27.         String content = "MyMiniCatServlet-get";
  28.         IMiniCatResponse miniCatResponse = (IMiniCatResponse) response;
  29.         miniCatResponse.write(InnerHttpUtil.http200Resp(content));
  30.         logger.info("MyMiniCatServlet-get-end");
  31.     }
  32.     @Override
  33.     public void doPost(HttpServletRequest request, HttpServletResponse response) {
  34.         String content = "MyMiniCatServlet-post";
  35.         IMiniCatResponse miniCatResponse = (IMiniCatResponse) response;
  36.         miniCatResponse.write(InnerHttpUtil.http200Resp(content));
  37.     }
  38. }
复制代码
应用启动解析

那么, 应该如何解析处理 servlet 呢?
DefaultServletManager

定义一个 servlet 的管理类
  1. package com.github.houbb.minicat.support.servlet.manager;
  2. import com.github.houbb.heaven.util.lang.StringUtil;
  3. import com.github.houbb.log.integration.core.Log;
  4. import com.github.houbb.log.integration.core.LogFactory;
  5. import com.github.houbb.minicat.exception.MiniCatException;
  6. import javax.servlet.http.HttpServlet;
  7. import java.util.HashMap;
  8. import java.util.Map;
  9. /**
  10. * servlet 管理
  11. *
  12. * 基于 web.xml 的读取解析
  13. * @since 0.5.0
  14. * @author 老马啸西风
  15. */
  16. public class DefaultServletManager implements IServletManager {
  17.     // 基础属性省略
  18.     @Override
  19.     public void register(String url, HttpServlet servlet) {
  20.         logger.info("[MiniCat] register servlet, url={}, servlet={}", url, servlet.getClass().getName());
  21.         servletMap.put(url, servlet);
  22.     }
  23.     @Override
  24.     public HttpServlet getServlet(String url) {
  25.         return servletMap.get(url);
  26.     }
  27. }
复制代码
register 的时机

以当地的 web.xml 解析为例

  • 解析对应的 web.xml 中的 servlet
  1. protected void processWebServlet(Element root, final IServletManager servletManager) {
  2.     Map<String, String> servletClassNameMap = new HashMap<>();
  3.     Map<String, String> urlPatternMap = new HashMap<>();
  4.     List<Element> servletElements = root.elements("servlet");
  5.     for (Element servletElement : servletElements) {
  6.         String servletName = servletElement.elementText("servlet-name");
  7.         String servletClass = servletElement.elementText("servlet-class");
  8.         servletClassNameMap.put(servletName, servletClass);
  9.     }
  10.     List<Element> urlMappingElements = root.elements("servlet-mapping");
  11.     for (Element urlElem : urlMappingElements) {
  12.         String servletName = urlElem.elementText("servlet-name");
  13.         String urlPattern = urlElem.elementText("url-pattern");
  14.         urlPatternMap.put(servletName, urlPattern);
  15.     }
  16.     handleServletConfigMap(servletClassNameMap, urlPatternMap, servletManager);
  17. }
复制代码

  • 注册对应的信息
  1. protected void handleServletConfigMap(Map<String, String> servletClassNameMap, Map<String, String> urlPatternMap, final IServletManager servletManager) {
  2.     try {
  3.         for (Map.Entry<String, String> urlPatternEntry : urlPatternMap.entrySet()) {
  4.             String servletName = urlPatternEntry.getKey();
  5.             String urlPattern = urlPatternEntry.getValue();
  6.             String className = servletClassNameMap.get(servletName);
  7.             if (StringUtil.isEmpty(className)) {
  8.                 throw new MiniCatException("className not found for servletName: " + servletName);
  9.             }
  10.             Class servletClazz = Class.forName(className);
  11.             HttpServlet httpServlet = (HttpServlet) servletClazz.newInstance();
  12.             // 构建
  13.             String fullUrlPattern = buildFullUrlPattern(urlPattern);
  14.             servletManager.register(fullUrlPattern, httpServlet);
  15.         }
  16.     } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
  17.         throw new MiniCatException(e);
  18.     }
  19. }
复制代码
调用的时机

servlet 注册好了,我们什么时间使用呢?
当然是根据请求所在 url 分发处理了。
  1. public class ServletRequestDispatcher implements IRequestDispatcher {
  2.     private static final Log logger = LogFactory.getLog(ServletRequestDispatcher.class);
  3.     public void dispatch(final IMiniCatRequest request,
  4.                          final IMiniCatResponse response,
  5.                          final MiniCatContextConfig config) {
  6.         final IServletManager servletManager = config.getServletManager();
  7.         // 直接和 servlet 映射
  8.         final String requestUrl = request.getUrl();
  9.         HttpServlet httpServlet = servletManager.getServlet(requestUrl);
  10.         if(httpServlet == null) {
  11.             logger.warn("[MiniCat] requestUrl={} mapping not found", requestUrl);
  12.             response.write(InnerHttpUtil.http404Resp());
  13.         } else {
  14.             // 正常的逻辑处理
  15.             try {
  16.                 httpServlet.service(request, response);
  17.             } catch (Exception e) {
  18.                 logger.error("[MiniCat] http servlet handle meet ex", e);
  19.                 throw new MiniCatException(e);
  20.             }
  21.         }
  22.     }
  23. }
复制代码
这样,一个简朴的 servlet 处理流程就实现了。
开源所在
  1. /\_/\  
  2. ( o.o )
  3. > ^ <
复制代码
mini-cat 是简易版本的 tomcat 实现。别称【嗅虎】(心有猛虎,轻嗅蔷薇。)
开源所在:https://github.com/houbb/minicat

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

种地

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表