首页 > 其他分享 >从零手写实现 tomcat-09-servlet 处理类

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

时间:2024-05-11 08:57:27浏览次数:23  
标签:github 请求 tomcat servlet 09 import Servlet

创作缘由

平时使用 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 吗?

<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
    <!-- servlet 配置 -->
    <servlet>
        <servlet-name>my</servlet-name>
        <servlet-class>com.github.houbb.minicat.support.servlet.MyMiniCatHttpServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>my</servlet-name>
        <url-pattern>/my</url-pattern>
    </servlet-mapping>
</web-app>

servlet 是什么?我们又该如何解析实现呢?

servlet 是什么?

Servlet可以被看作是Tomcat中的一个“服务员”。

就像餐厅里的服务员负责接待顾客、接收点餐、上菜等任务一样,Servlet在Web服务器中负责处理网络请求,并返回响应结果。

Servlet的主要工作包括:

  1. 接收请求:当浏览器(客户端)发送一个HTTP请求到Tomcat时,这个请求会被映射到一个特定的Servlet。

  2. 处理请求:Servlet会根据请求的类型(比如GET或POST)和内容,执行相应的处理逻辑。比如,如果请求是要显示一个网页,Servlet就会生成这个网页的内容。

  3. 生成响应:处理完请求后,Servlet会生成一个HTTP响应,这个响应包含了客户端需要的信息,比如网页内容、图片、视频等。

  4. 返回响应:最后,Servlet把生成的响应返回给客户端,客户端收到响应后,就可以展示网页或者进行其他操作。

servlet 处理流程

  1. 客户端(比如浏览器)发送一个HTTP请求到Tomcat。

  2. Tomcat的请求分发器(RequestDispatcher)会根据请求的URL,找到对应的Servlet。

  3. Tomcat调用Servlet的service()方法,把请求交给Servlet处理。在service()方法内部,Servlet会根据HTTP请求的方法(GET、POST等)调用相应的处理方法。

  4. Servlet处理请求,并生成响应。比如,如果是GET请求,Servlet可能会查询数据库,生成一个网页;如果是POST请求,Servlet可能会处理表单数据,执行一些业务逻辑。

  5. Servlet把生成的响应返回给Tomcat。

  6. Tomcat把响应发送回客户端。

通过使用Servlet,你可以灵活地处理各种HTTP请求,并生成相应的响应。

举个板栗

好的,让我们用餐厅的例子来比喻Tomcat中的Servlet:

想象一下,你走进一家餐厅,服务员会过来接待你。

服务员会问你需要什么服务,这就像是HTTP请求。在Tomcat中,Servlet就扮演了服务员的角色。

  1. 接收点餐:当顾客(客户端)进入餐厅(访问网站),服务员(Servlet)会过来记录顾客的点餐(接收HTTP请求)。

  2. 处理点餐:服务员会根据顾客的点餐内容(请求类型,如GET或POST),去厨房(后端逻辑)准备食物(处理请求)。

  3. 上菜:食物准备好后,服务员会将食物(响应内容)端上桌(生成HTTP响应)。

  4. 结账:顾客享用完毕后,服务员会拿来账单(请求结束,返回响应),顾客结账离开。

Servlet通过这种方式,可以处理各种类型的点餐(请求),无论是简单的查看菜单(静态页面请求),还是复杂的定制菜品(复杂的业务逻辑请求)。

通过合理设计Servlet,餐厅(网站)可以提供丰富多样的服务(功能)。

自己实现

接口定义

这里就不定义了,直接复用 servlet 的标准 api

抽象类

我们实现一个基础的抽象类:

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

import com.github.houbb.minicat.constant.HttpMethodType;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public abstract class AbstractMiniCatHttpServlet extends HttpServlet {

    public abstract void doGet(HttpServletRequest request, HttpServletResponse response);

    public abstract void doPost(HttpServletRequest request, HttpServletResponse response);

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) req;
        HttpServletResponse httpServletResponse = (HttpServletResponse) res;
        if(HttpMethodType.GET.getCode().equalsIgnoreCase(httpServletRequest.getMethod())) {
            this.doGet(httpServletRequest, httpServletResponse);
            return;
        }

        this.doPost(httpServletRequest, httpServletResponse);
    }

}

接口实现

简单的实现

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

import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.minicat.dto.IMiniCatResponse;
import com.github.houbb.minicat.dto.MiniCatResponseBio;
import com.github.houbb.minicat.util.InnerHttpUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 仅用于测试
 *
 * @since 0.3.0
 * @author 老马啸西风
 */
public class MyMiniCatHttpServlet extends AbstractMiniCatHttpServlet {

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


    // 方法实现
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        logger.info("MyMiniCatServlet-get");
        // 模拟耗时
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String content = "MyMiniCatServlet-get";
        IMiniCatResponse miniCatResponse = (IMiniCatResponse) response;
        miniCatResponse.write(InnerHttpUtil.http200Resp(content));
        logger.info("MyMiniCatServlet-get-end");
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) {
        String content = "MyMiniCatServlet-post";

        IMiniCatResponse miniCatResponse = (IMiniCatResponse) response;
        miniCatResponse.write(InnerHttpUtil.http200Resp(content));
    }

}

应用启动解析

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

DefaultServletManager

定义一个 servlet 的管理类

package com.github.houbb.minicat.support.servlet.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 javax.servlet.http.HttpServlet;
import java.util.HashMap;
import java.util.Map;

/**
 * servlet 管理
 *
 * 基于 web.xml 的读取解析
 * @since 0.5.0
 * @author 老马啸西风
 */
public class DefaultServletManager implements IServletManager {

    // 基础属性省略

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

        servletMap.put(url, servlet);
    }

    @Override
    public HttpServlet getServlet(String url) {
        return servletMap.get(url);
    }

}

register 的时机

以本地的 web.xml 解析为例

  1. 解析对应的 web.xml 中的 servlet
protected void processWebServlet(Element root, final IServletManager servletManager) {
    Map<String, String> servletClassNameMap = new HashMap<>();
    Map<String, String> urlPatternMap = new HashMap<>();
    List<Element> servletElements = root.elements("servlet");
    for (Element servletElement : servletElements) {
        String servletName = servletElement.elementText("servlet-name");
        String servletClass = servletElement.elementText("servlet-class");
        servletClassNameMap.put(servletName, servletClass);
    }
    List<Element> urlMappingElements = root.elements("servlet-mapping");
    for (Element urlElem : urlMappingElements) {
        String servletName = urlElem.elementText("servlet-name");
        String urlPattern = urlElem.elementText("url-pattern");
        urlPatternMap.put(servletName, urlPattern);
    }
    handleServletConfigMap(servletClassNameMap, urlPatternMap, servletManager);
}
  1. 注册对应的信息
protected void handleServletConfigMap(Map<String, String> servletClassNameMap, Map<String, String> urlPatternMap, final IServletManager servletManager) {
    try {
        for (Map.Entry<String, String> urlPatternEntry : urlPatternMap.entrySet()) {
            String servletName = urlPatternEntry.getKey();
            String urlPattern = urlPatternEntry.getValue();
            String className = servletClassNameMap.get(servletName);
            if (StringUtil.isEmpty(className)) {
                throw new MiniCatException("className not found for servletName: " + servletName);
            }
            Class servletClazz = Class.forName(className);
            HttpServlet httpServlet = (HttpServlet) servletClazz.newInstance();
            // 构建
            String fullUrlPattern = buildFullUrlPattern(urlPattern);
            servletManager.register(fullUrlPattern, httpServlet);
        }
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        throw new MiniCatException(e);
    }
}

调用的时机

servlet 注册好了,我们什么时候使用呢?

当然是根据请求地址 url 分发处理了。

public class ServletRequestDispatcher implements IRequestDispatcher {

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

    public void dispatch(final IMiniCatRequest request,
                         final IMiniCatResponse response,
                         final MiniCatContextConfig config) {
        final IServletManager servletManager = config.getServletManager();

        // 直接和 servlet 映射
        final String requestUrl = request.getUrl();
        HttpServlet httpServlet = servletManager.getServlet(requestUrl);
        if(httpServlet == null) {
            logger.warn("[MiniCat] requestUrl={} mapping not found", requestUrl);
            response.write(InnerHttpUtil.http404Resp());
        } else {
            // 正常的逻辑处理
            try {
                httpServlet.service(request, response);
            } catch (Exception e) {
                logger.error("[MiniCat] http servlet handle meet ex", e);

                throw new MiniCatException(e);
            }
        }
    }
}

这样,一个简单的 servlet 处理流程就实现了。

开源地址

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

mini-cat 是简易版本的 tomcat 实现。别称【嗅虎】(心有猛虎,轻嗅蔷薇。)

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

标签:github,请求,tomcat,servlet,09,import,Servlet
From: https://www.cnblogs.com/houbbBlogs/p/18185677

相关文章

  • 代码随想录训练营第二天 | 977.有序数组的平方 209.长度最小的子数组 59.螺旋矩阵II
    977.有序数组的平方题目链接:https://leetcode.cn/problems/squares-of-a-sorted-array/文章讲解:https://programmercarl.com/0977.有序数组的平方.html视频讲解:https://www.bilibili.com/video/BV1QB4y1D7ep暴力解时间复杂度O(nlogn)空间复杂度O(1)双指针法时间复......
  • 【2024-05-09】考验心力
    20:00也许这就是幸福:感觉不应该在其它地方,不应该在做其它的事情,不应该做其他人。                                                 ——艾萨克·阿西莫夫早上上班的时......
  • Docker09-Docker API
    9.1DockerAPIDockerAPI种类DockerRemoteAPI:  dockerrun等操作均是通过调用该API向DockerDaemon发起请求的DockerRegistryAPIDockerHubAPIdockerremoteAPI容器列表--获取所有容器的清单 GET/containers/jsoncurl-XGEThttp://127.0.0.1:9999/conta......
  • 从零手写实现 tomcat-07-war 如何解析处理三方的 war 包?
    创作缘由平时使用tomcat等web服务器不可谓不多,但是一直一知半解。于是想着自己实现一个简单版本,学习一下tomcat的精髓。系列教程从零手写实现apacheTomcat-01-入门介绍从零手写实现apacheTomcat-02-web.xml入门详细介绍从零手写实现tomcat-03-基本的socket实......
  • loons2024年05月09日20:04:57
        1       1 11 1111 1111 1111 1111 1111 1111 ......
  • 20240509线上问题排查
    iotop-oPpidstat-d1mpstat-PALL5cat/proc/*/status|grep-E'State:.*Z.*|State:.*D.*'-A10-B2tophttps://blog.csdn.net/chrisy521/article/details/128532234db.system.profile.find({"millis":{$gte:500}}).limit(10).sort({ts:......
  • bd09坐标转wgs84
    之前公司定位用的是百度定位,但是由于公司地图展示位天地图,由于偏移严重(毕竟坐标系不同)需要坐标系转换,之前看公司的处理逻辑是联网纠偏(非公司内部服务),一直也能延用,近期由于外网服务不能使用服务迁至阿里,无法使用,所以需要另谋方式,功夫不负有心人总算找到上源码(亲测准确)package......
  • 20240509xxx集群xx节点PLEG超时问题
    20240509xxx集群xx节点PLEG超时问题//20240509写在前面xxx集群xx节点又又又又又卡住了,经过一系列排查,终于解决了问题,由于这次找到了通用解法,所以在此记录下ps:国内的搜索引擎是真的shi。。搜出来的帖子都是抄来抄去的,还不解决问题,还得是google/大拇指问题起因:偷得浮生半日......
  • Linux从入门到精通——Centos 7.9.2009 配置国内yum源及epel源
    Centos7.9.2009配置国内yum源及epel源一、备份原有的yum源配置文件在进行任何更改之前,建议先备份原有的yum源配置文件,以防止配置过程中出现问题。可以使用以下命令备份CentOS-Base.repo文件:[root@localhost~]#cat/etc/redhat-releaseCentOSLinuxrelease7.9.2009(Cor......
  • 09. C语言内嵌汇编代码
    C语言函数内可以自定义一段汇编代码,在GCC编译器中使用asm或__asm__关键词定义一段汇编代码,并可选添加volatile关键字,表示不要让编译器优化这段汇编代码。内嵌汇编代码格式如下:__asm__(  "汇编代码"  :输出描述  :输入描述  :修改描述);汇编代码部分......