如何基于Netty手写简单的Tomcat?

news2024/11/20 13:38:46

如何基于Netty手写简单的Tomcat?

我们最常用的服务器是tomcat ,我们使用tomcat 也主要作为http服务器 。

http协议是基于TCP 协议,换句话说使用socket 或者 NIO编程,只要能正确的解析http报文,然后将结果按照 http 报文的格式返回。我们就可以实现自己的web服务器,当然再支持websocket 也是可以做到的。

Netty 给我们内置了http 、websocket 处理器,直接拿来使用即可,因此基于Netty 会简化很多。

手写tomcat 核心是什么呢?

服务器就是处理用户请求,并返回结果。使用过curl 命令的应该印象很深刻,http 请求最主要的是url 和参数(包含请求头)

URL 就是告诉我们我要做什么功能(what),而参数就是告诉系统具体怎样做(How)。

HTTP 报文示例

POST /login HTTP/1.1
Host: api.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
Connection: keep-alive

username=admin&password=123456

举个不恰当例子

  • 前端调用 /user/login,说明用户想登录了。

  • 请求参数包含了用户名、密码,该用户选择了用户名、密码进行登录。

  • 当然系统也支持短信验证码登录方式, 如果用户选择该方式,请求参数必然包含手机号和短信验证码。

如何根据URL 确定相应的业务处理逻辑

通过上面分析,通请求过来,我们需要根据 url 找到对应的处理器 ,比如下图, 前端调用 /user/login 这个接口,后端如何就能会执行到 登录相关业务代码呢?

图片

使用HashMap 做个映射可否

图片

很容易想到用 HashMap 记录url到对应的处理器不就可以了,直接硬编码肯定是不妥的,作为一个通用的服务器比如tomcat ,你不可能每次实现一个新业务,让tomcat为你修改源码。因此才有了外部配置文件。

配置文件方式

在tomcat 中我们可以在web.xml中配置servlet-mapping,来确认url匹配对应的Servlet, 启动时加载配置文件,注册url 与Servlet映射关系,收到请求,就能根据url 找到 对应Servlet 处理了。

实际上根据url查找对应的Servlet 不一定是简单的 hashMap.get() 就可以了, 因为servlet-mapping 中配置的URL 可能包含一些通配符,tomcat内部url 匹配算法,优先匹配最精确的那个。

Tomcat使用的是XML方式进行动态配置,抛开Tomcat,就实现这种映射关系可以是各种配置,yaml,json等,只要能满足动态配置即可,当然要考虑使用简单。

  <servlet-mapping>
    <servlet-name>LoginServlet</servlet-name>
      <url-pattern>/user/login</url-pattern>
  </servlet-mapping>

自定义注解

Spring MVC 处理业务逻辑的DispatcherServlet,我们感知不到它的存在,平常我们只需要写Controller ,这是因为DispatcherServlet拦截了所有的请求。

SpringBoot 提倡零配置,使用注解方式是Spring MVC 的主流。

Spring 容器启动时会扫描 @Controller @RestController  相关类,并且j解析类上、方法上 @RequestMapping 注解。

类上URL作为基础路径和方法上URL拼接作为最终URL ,而对应方法即最终URL的处理器。相关的映射关系也被称为handler mappings 。

状态码

对于Socket本身其实只能绑定端口,当用户URL找不到对应处理器,Tomcat一般会报404 。

当然Http 标准定义了很多状态码,如200 代表请求成功等,处理出错了会报500等

Netty http 示例

服务端pipeline 配置如下,HttpServerHandler 是一个简单的处理器。

  • QueryStringDecoder 可以用来处理GET请求参数解析

  • HttpPostRequestDecoder 用来解析POST请求参数

  • FullHttpResponse 用来响应请求

读者自己加上根据URL 进行不同处理逻辑,就是一个简单的HTTP服务器了,限于篇幅,具体实现省略。

    ChannelPipeline p = ch.pipeline();
    // 添加 HTTP 请求解码器
    p.addLast(new HttpRequestDecoder());
    // 添加 HTTP 响应编码器
    p.addLast(new HttpResponseEncoder());
    // 聚合HTTP消息,将多个部分组合成一个完整的请求
    p.addLast(new HttpObjectAggregator(65536));
    // 添加自定义处理器
    p.addLast(new HttpServerHandler());
public class HttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {

        if (req.method().equals(HttpMethod.GET)) {
            QueryStringDecoder decoder = new QueryStringDecoder(req.uri());
            Map<String, List<String>> paramers = decoder.parameters();
            if (paramers != null) {
                paramers.forEach((key, value) -> System.out.println(key + " => " + value));
            }
        } else {
            HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req);

            List<InterfaceHttpData> datas = decoder.getBodyHttpDatas();
            for (InterfaceHttpData data : datas) {
                if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
                    MixedAttribute attribute = (MixedAttribute) data;
                    System.out.println(attribute.getName() + " => " + attribute.getValue());
                }

            }
        }


        // 构造响应
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                HttpResponseStatus.OK, Unpooled.wrappedBuffer("Hello, World!".getBytes()));

        // 设置响应头
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());

        // 发送响应
        ctx.writeAndFlush(response);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

演示结果

图片

图片

webSocket

服务端核心代码

   ch.pipeline().addLast(new HttpServerCodec());
                     ch.pipeline().addLast(new ChunkedWriteHandler());
                     ch.pipeline().addLast(new HttpObjectAggregator(65536));
                     ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws"));
                     ch.pipeline().addLast(new WebSocketServerHandler());
public class WebSocketServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        // 输出接收到的消息
        System.out.println("Received message: " + msg.text());

        // 回应客户端
        String response = "Server received: " + msg.text();
        ctx.channel().writeAndFlush(new TextWebSocketFrame(response));
    }
    //省略其他方法
}

演示效果

演示时可以使用 java websocket client

       WebSocketContainer container = ContainerProvider.getWebSocketContainer();

            // 连接到WebSocket服务器
            String uri = "ws://localhost:8080/ws";
            logger.info("Connecting to " + uri);
            Session session = container.connectToServer(MyWebSocketClient.class, URI.create(uri));

            // 等待一段时间以保持连接
            Thread.sleep(10000);

            // 关闭连接
            session.close();
@ClientEndpoint
public class MyWebSocketClient {

    //省略非重要代码
    
    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        logger.info("Connected to server: " + session.getId());
        // 发送消息到服务器
        session.getAsyncRemote().sendText("Hello, Server!");
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        logger.info("Received from server: " + message);
    }

}

图片

总结

本文简单梳理了Tomcat 、Spring MVC 通过URL 找对应的处理器的流程,为手写Tomcat 提供了思路。给出了基于Netty 支持Http 和 WebSocket示例代码。

至此手写一个简单的Http服务器,相信不是什么难事。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2244063.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RabbitMQ教程:发布/订阅模式(Publish/Subscribe)(三)

文章目录 RabbitMQ教程&#xff1a;发布/订阅模式&#xff08;Publish/Subscribe&#xff09;&#xff08;三&#xff09;一、引言二、简介三、准备工作3.1 说明3.2 生成项目 四、实战4.1 交换机&#xff08;Exchanges&#xff09;4.2 临时队列&#xff08;Temporary Queues&am…

金山云Q3调整后EBITDA率提升至9.8% 经营效率和盈利能力强劲增长

11月19日&#xff0c;金山云公布了2024年第三季度业绩。 季度内&#xff0c;公司在收入规模、盈利能力、经营现金流方面都取得了扎实的进展。财报显示&#xff0c;金山云Q3营收18.9亿元&#xff0c;同比回归两位数快速增长&#xff0c;达16.0%&#xff1b;公有云实现收入11.8亿…

Python轴承故障诊断 (19)基于Transformer-BiLSTM的创新诊断模型

往期精彩内容&#xff1a; Python-凯斯西储大学&#xff08;CWRU&#xff09;轴承数据解读与分类处理 Pytorch-LSTM轴承故障一维信号分类(一)-CSDN博客 Pytorch-CNN轴承故障一维信号分类(二)-CSDN博客 Pytorch-Transformer轴承故障一维信号分类(三)-CSDN博客 三十多个开源…

Linux 安装 jdk8

将原有的 JDK 卸载干净&#xff08;可选&#xff09; # 查找并显示出系统所有已安装的与 JDK 相关的 rpm 软件包名称 rpm -qa | grep jdk # 删除 jdk rpm -e --nodeps 要卸载的JDK 安装 一、方法一&#xff1a;yum 包管理器安装 1&#xff09;检索可用包 yum search java |…

ESLint的简单使用(js,ts,vue)

一、ESLint介绍 1.为什么要用ESLint 统一团队编码规范&#xff08;命名&#xff0c;格式等&#xff09; 统一语法 减少git不必要的提交 减少低级错误 在编译时检查语法&#xff0c;而不是等js引擎运行时才检查 2.eslint用法 可以手动下载配置 可以通过vue脚手架创建项…

11.19机器学习_逻辑回归

十二 逻辑回归 1.概念 逻辑回归(Logistic Regression)是机器学习中的一种分类模型&#xff0c;逻辑回归是一种分类算法&#xff0c;虽然名字中带有回归&#xff0c;但是它与回归之间有一定的联系。由于算法的简单和高效&#xff0c;在实际中应用非常广泛。 逻辑回归一般用于…

数据结构-树状数组专题(2)

一、前言 接上回树状数组专题&#xff08;1&#xff09;&#xff0c;这次主要介绍差分跟树状数组联动实现区间更新 二、我的模板 重新放了一遍&#xff0c;还是提一嘴&#xff0c;注意下标从0开始&#xff0c;区间左闭右开 template <typename T> struct Fenwick {in…

SAM-Med2D 训练完成后boxes_prompt没有生成mask的问题

之前对着这这篇文章去微调SAM_Med2D(windows环境),发现boxes_prompt空空如也。查找了好长时间问题SAM-Med2D 大模型学习笔记&#xff08;续&#xff09;&#xff1a;训练自己数据集_sam训练自己数据集-CSDN博客 今天在看label2image_test.json文件的时候发现了一些端倪: 官方…

从源头保障电力安全:输电线路动态增容与温度监测技术详解

在电力系统中&#xff0c;输电线路是电能传输的关键环节。然而&#xff0c;当导线温度过高时&#xff0c;会加速导线老化&#xff0c;降低绝缘性能&#xff0c;甚至引发短路、火灾等严重事故&#xff0c;对电网安全运行构成巨大威胁。近日&#xff0c;某地区因持续高温和用电负…

第02章 CentOS基本操作

2.文件基本操作【文件操作&#xff08;一&#xff09;】 目标 理解Linux下路径的表示方法能够使用命令(mkdir和touch)在指定位置创建目录和文件能够使用命令(rm)删除指定的目录和文件能够使用命令(ls)列出目录里的文件能够使用命令(cat,head,tail,less,more)查看文件内容理解标…

leetcode400第N位数字

代码 class Solution {public int findNthDigit(int n) {int base 1;//位数int weight 9;//权重while(n>(long)base*weight){//300n-base*weight;base;weight*10;}//n111 base3 weight900;n--;int res (int)Math.pow(10,base-1)n/base;int index n%base;return String…

工业生产安全-安全帽第二篇-用java语言看看opencv实现的目标检测使用过程

一.背景 公司是非煤采矿业&#xff0c;核心业务是采选&#xff0c;大型设备多&#xff0c;安全风险因素多。当下政府重视安全&#xff0c;头部技术企业的安全解决方案先进但价格不低&#xff0c;作为民营企业对安全投入的成本很敏感。利用我本身所学&#xff0c;准备搭建公司的…

【AI人脸工具整合包及教程】Rope——重新定义你的数字形象!

引言 在这个数字时代&#xff0c;个人形象的重要性不言而喻。无论是社交媒体上的个人展示&#xff0c;还是商业活动中的品牌塑造&#xff0c;一个独特的形象都能让人眼前一亮。随着技术的发展&#xff0c;AI人脸技术逐渐从科幻走向现实&#xff0c;成为普通人也能轻松触及的技…

NLP论文速读(EMNLP 2024)|动态奖励与提示优化来帮助语言模型的进行自我对齐

论文速读|Dynamic Rewarding with Prompt Optimization Enables Tuning-free Self-Alignment of Language Models 论文信息&#xff1a; 简介: 本文讨论的背景是大型语言模型&#xff08;LLMs&#xff09;的自我对齐问题。传统的LLMs对齐方法依赖于昂贵的训练和人类偏好注释&am…

java CAS详解

java 中CAS是如何实现的&#xff1f; 在 Java 中&#xff0c;实现 CAS&#xff08;Compare-And-Swap, 比较并交换&#xff09;操作的一个关键类是Unsafe。 Unsafe类位于sun.misc包下&#xff0c;是一个提供低级别、不安全操作的类。由于其强大的功能和潜在的危险性&#xff0…

Gooxi受邀参加海通证券AI+应用生态大会,助力数智金融高质量发展

11月15日&#xff0c;由海通证券举办以”智算无界&#xff0c;共臻高远”为主题AI应用生态大会在上海圆满落幕。此次活动汇聚了众多人工智能领域的意见领袖、专家学者、优秀企业代表及资深投资人&#xff0c;共同探讨金融行业人工智能应用的前沿理论、最佳实践及发展趋势&#…

Python数据分析与可视化实验案例,所需数据已经绑定上传

大数据技术专业技能竞赛试卷 一、项目名称 农业肥料登记数据分析赛题 二、竞赛内容 赛项以大数据技术为核心内容&#xff0c;重点考查参赛选手数据清洗和数据分析的能力&#xff0c;结合Pandas和matplotlib图表展示数据。所有参赛学生在现场根据给定的项目任务&#xff0c;…

【竞技宝】LOL-传奇杯:姿态飞机TP绕后一锤定音

北京时间2024年11月19日,英雄联盟第二届传奇杯正在如火如荼的进行之中。昨天迎来小组赛第四个比赛日,本日一共进行了七场小组赛的对决,那么在昨日上半场的四场比赛中,登场的各支队伍都取得了什么样的表现呢?接下来小宝为大家带来小组赛day4上半场的比赛战报。 OP(宁王队) 0-1 …

qt之telnet连接目标设备在线调试功能

一、前言 在QT下使用telnet连接目标设备&#xff0c;进行在线命令调试&#xff0c;也可配合ftp或ssh使用。 telnet某些库在qt5下不可用&#xff0c;无法获取登录信息&#xff0c;只能获取到连接信息&#xff0c;这里我用自己的方式判断是否成功登录 二、环境 window qt5.7…

Android中常见内存泄漏的场景和解决方案

本文讲解Android 开发中常见内存泄漏场景及其解决方案&#xff0c;内容包括代码示例、原因分析以及最佳实践建议。 1. 静态变量导致的内存泄漏 静态变量的生命周期与应用进程一致&#xff0c;如果静态变量持有了对 Activity 或其他大对象的引用&#xff0c;就可能导致内存泄漏…