欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > IT业 > 如何基于Netty手写简单的Tomcat?

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

2025/10/20 14:11:32 来源:https://blog.csdn.net/happycao123/article/details/143894370  浏览:    关键词:如何基于Netty手写简单的Tomcat?

如何基于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-aliveusername=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> {@Overrideprotected 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);}@Overridepublic 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> {@Overrideprotected 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 {//省略非重要代码@OnOpenpublic void onOpen(Session session, EndpointConfig config) {logger.info("Connected to server: " + session.getId());// 发送消息到服务器session.getAsyncRemote().sendText("Hello, Server!");}@OnMessagepublic void onMessage(String message, Session session) {logger.info("Received from server: " + message);}}

图片

总结

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

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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词