从零手写实现 tomcat-05-servlet 处理支持

打印 上一主题 下一主题

主题 852|帖子 852|积分 2556

创作缘由

平时使用 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 监听器
团体思绪

模仿实现 servlet 的逻辑处理,而不是局限于上一节的静态文件资源。
团体流程

1)界说 servlet 标准的 接口+实现
2)解析 web.xml 获取对应的 servlet 实例与 url 之间的映射关系。
3)调用哀求
1. servlet 实现

api 接口

servlet 接口,我们直接引入 servlet-api 的标准。
  1. <dependency>
  2.     <groupId>javax.servlet</groupId>
  3.     <artifactId>javax.servlet-api</artifactId>
  4.     <version>${javax.servlet.version}</version>
  5. </dependency>
复制代码
抽象 servlet 界说
  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. }
复制代码
根据哀求方式分别处理
简单的实现例子

下面是一个简单的处理实现:

  • MyMiniCatHttpServlet.java
  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.MiniCatResponse;
  5. import com.github.houbb.minicat.util.InnerHttpUtil;
  6. import javax.servlet.http.HttpServletRequest;
  7. import javax.servlet.http.HttpServletResponse;
  8. /**
  9. * 仅用于测试
  10. *
  11. * @since 0.3.0
  12. */
  13. public class MyMiniCatHttpServlet extends AbstractMiniCatHttpServlet {
  14.     private static final Log logger = LogFactory.getLog(MyMiniCatHttpServlet.class);
  15.     @Override
  16.     public void doGet(HttpServletRequest request, HttpServletResponse response) {
  17.         String content = "MyMiniCatServlet-get";
  18.         MiniCatResponse miniCatResponse = (MiniCatResponse) response;
  19.         miniCatResponse.write(InnerHttpUtil.http200Resp(content));
  20.     }
  21.     @Override
  22.     public void doPost(HttpServletRequest request, HttpServletResponse response) {
  23.         String content = "MyMiniCatServlet-post";
  24.         MiniCatResponse miniCatResponse = (MiniCatResponse) response;
  25.         miniCatResponse.write(InnerHttpUtil.http200Resp(content));
  26.     }
  27. }
复制代码
2. web.xml 解析

阐明

web.xml 需要解析处理。
比如这样的:
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <web-app>
  3.     <servlet>
  4.         <servlet-name>my</servlet-name>
  5.         <servlet-class>com.github.houbb.minicat.support.servlet.MyMiniCatHttpServlet</servlet-class>
  6.     </servlet>
  7.     <servlet-mapping>
  8.         <servlet-name>my</servlet-name>
  9.         <url-pattern>/my</url-pattern>
  10.     </servlet-mapping>
  11. </web-app>
复制代码
解析方式

接口界说
  1. package com.github.houbb.minicat.support.servlet;
  2. import javax.servlet.Servlet;
  3. import javax.servlet.http.HttpServlet;
  4. /**
  5. * servlet 管理
  6. *
  7. * @since 0.3.0
  8. */
  9. public interface IServletManager {
  10.     /**
  11.      * 注册 servlet
  12.      *
  13.      * @param url     url
  14.      * @param servlet servlet
  15.      */
  16.     void register(String url, HttpServlet servlet);
  17.     /**
  18.      * 获取 servlet
  19.      *
  20.      * @param url url
  21.      * @return servlet
  22.      */
  23.     HttpServlet getServlet(String url);
  24. }
复制代码
web.xml

web.xml 的解析方式,核心的处理方式:
  1.     //1. 解析 web.xml
  2.     //2. 读取对应的 servlet mapping
  3.     //3. 保存对应的 url + servlet 示例到 servletMap
  4.     private void loadFromWebXml() {
  5.         InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
  6.         SAXReader saxReader = new SAXReader();
  7.         try {
  8.             Document document = saxReader.read(resourceAsStream);
  9.             Element rootElement = document.getRootElement();
  10.             List<Element> selectNodes = rootElement.selectNodes("//servlet");
  11.             //1, 找到所有的servlet标签,找到servlet-name和servlet-class
  12.             //2, 根据servlet-name找到<servlet-mapping>中与其匹配的<url-pattern>
  13.             for (Element element : selectNodes) {
  14.                 /**
  15.                  * 1, 找到所有的servlet标签,找到servlet-name和servlet-class
  16.                  */
  17.                 Element servletNameElement = (Element) element.selectSingleNode("servlet-name");
  18.                 String servletName = servletNameElement.getStringValue();
  19.                 Element servletClassElement = (Element) element.selectSingleNode("servlet-class");
  20.                 String servletClass = servletClassElement.getStringValue();
  21.                 /**
  22.                  * 2, 根据servlet-name找到<servlet-mapping>中与其匹配的<url-pattern>
  23.                  */
  24.                 //Xpath表达式:从/web-app/servlet-mapping下查询,查询出servlet-name=servletName的元素
  25.                 Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']'");
  26.                 String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
  27.                 HttpServlet httpServlet = (HttpServlet) Class.forName(servletClass).newInstance();
  28.                 this.register(urlPattern, httpServlet);
  29.             }
  30.         } catch (Exception e) {
  31.             logger.error("[MiniCat] read web.xml failed", e);
  32.             throw new MiniCatException(e);
  33.         }
  34.     }
复制代码
解析之后的 HttpServlet 全部放在 servletMap 中。
然后在对应的 url 我们选取处理即可。
3. url 的处理

阐明

根据 url 找到对应的 servlet 进行处理。
主要分为 3 大类:
1)url 不存在
2)url 为 html 等静态资源

  • servlet 的处理逻辑
设计

我们把这部门抽象为接口:
  1. public void dispatch(RequestDispatcherContext context) {
  2.     final MiniCatRequest request = context.getRequest();
  3.     final MiniCatResponse response = context.getResponse();
  4.     final IServletManager servletManager = context.getServletManager();
  5.     // 判断文件是否存在
  6.     String requestUrl = request.getUrl();
  7.     if (StringUtil.isEmpty(requestUrl)) {
  8.         emptyRequestDispatcher.dispatch(context);
  9.     } else {
  10.         // 静态资源
  11.         if (requestUrl.endsWith(".html")) {
  12.             staticHtmlRequestDispatcher.dispatch(context);
  13.         } else {
  14.             // servlet
  15.             servletRequestDispatcher.dispatch(context);
  16.         }
  17.     }
  18. }
复制代码
servlet 例子

如果是 servlet 的话,核心处理逻辑如下:
  1. // 直接和 servlet 映射
  2. final String requestUrl = request.getUrl();
  3. HttpServlet httpServlet = servletManager.getServlet(requestUrl);
  4. if(httpServlet == null) {
  5.     logger.warn("[MiniCat] requestUrl={} mapping not found", requestUrl);
  6.     response.write(InnerHttpUtil.http404Resp());
  7. } else {
  8.     // 正常的逻辑处理
  9.     try {
  10.         httpServlet.service(request, response);
  11.     } catch (Exception e) {
  12.         logger.error("[MiniCat] http servlet handle meet ex", e);
  13.         throw new MiniCatException(e);
  14.     }
  15. }
复制代码
4. 读取 request 的问题修复

问题

发现 request 读取输入流的时候,有时候读取为空,但是页面明明是正常哀求的。
原始代码
  1. private void readFromStream() {
  2.     try {
  3.         //从输入流中获取请求信息
  4.         int count = inputStream.available();
  5.         byte[] bytes = new byte[count];
  6.         int readResult = inputStream.read(bytes);
  7.         String inputsStr = new String(bytes);
  8.         logger.info("[MiniCat] readCount={}, input stream {}", readResult, inputsStr);
  9.         if(readResult <= 0) {
  10.             logger.info("[MiniCat] readCount is empty, ignore handle.");
  11.             return;
  12.         }
  13.         //获取第一行数据
  14.         String firstLineStr = inputsStr.split("\\n")[0];  //GET / HTTP/1.1
  15.         String[] strings = firstLineStr.split(" ");
  16.         this.method = strings[0];
  17.         this.url = strings[1];
  18.         logger.info("[MiniCat] method={}, url={}", method, url);
  19.     } catch (IOException e) {
  20.         logger.error("[MiniCat] readFromStream meet ex", e);
  21.         throw new RuntimeException(e);
  22.     }
  23. }
复制代码
mini-cat 是简易版本的 tomcat 实现。别称【嗅虎】(心有猛虎,轻嗅蔷薇。)
开源地址:https://github.com/houbb/minicat

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

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

悠扬随风

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