前言
大家好,我是老马。很高兴遇到你。
我们希望实现最简单的 http 服务信息,可以处理静态文件。
如果你想知道 servlet 如何处理的,可以参考我的另一个项目:
手写从零实现简易版 tomcat minicat
netty 相关
如果你对 netty 不是很熟悉,可以读一下
Netty 权威指南-04-为什么选择 Netty?Netty 入门教程
手写 nginx 系列
如果你对 nginx 原理感兴趣,可以阅读:
从零手写实现 nginx-01-为什么不能有 java 版本的 nginx?
从零手写实现 nginx-03-nginx 基于 Netty 实现
从零手写实现 nginx-04-基于 netty http 出入参优化处理
从零手写实现 nginx-05-MIME类型(Multipurpose Internet Mail Extensions,多用途互联网邮件扩展类型)
从零手写实现 nginx-12-keep-alive 连接复用
从零手写实现 nginx-13-nginx.conf 配置文件介绍
从零手写实现 nginx-14-nginx.conf 和 hocon 格式有关系吗?
从零手写实现 nginx-15-nginx.conf 如何通过 java 解析处理?
从零手写实现 nginx-16-nginx 支持配置多个 server
基本实现
思路
上一节我们实现了基于 serverSocket 的处理监听,那个性能毕竟一般。
这一次我们一起通过 netty 对代码进行改造升级。
核心实现
启动类
INginxServer 接口不变,我们加一个 netty 的实现类。
这里针对 EventLoopGroup 的配置我们暂时使用默认值,后续可以考虑可以让用户自定义。
java
/**
* netty 实现
*
* @author 老马啸西风
* @since 0.2.0
*/
public class NginxServerNetty implements INginxServer {
private static final Log log = LogFactory.getLog(NginxServerNetty.class);
private NginxConfig nginxConfig;
@Override
public void init(NginxConfig nginxConfig) {
this.nginxConfig = nginxConfig;
}
@Override
public void start() {
// 服务器监听的端口号
String host = InnerNetUtil.getHost();
int port = nginxConfig.getHttpServerListen();
EventLoopGroup bossGroup = new NioEventLoopGroup();
//worker 线程池的数量默认为 CPU 核心数的两倍
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NginxNettyServerHandler(nginxConfig));
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// Bind and start to accept incoming connections.
ChannelFuture future = serverBootstrap.bind(port).sync();
log.info("[Nginx4j] listen on http://{}:{}", host, port);
// Wait until the server socket is closed.
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("[Nginx4j] start meet ex", e);
throw new Nginx4jException(e);
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
log.info("[Nginx4j] shutdownGracefully", host, port);
}
}
}
处理类
核心的处理逻辑都在 NginxNettyServerHandler 这个类。
这里主要做 3 件事
-
解析请求类
-
根据请求获取对应的内容
-
返回响应内容
java
/**
* netty 处理类
* @author 老马啸西风
* @since 0.2.0
*/
public class NginxNettyServerHandler extends ChannelInboundHandlerAdapter {
private static final Log logger = LogFactory.getLog(NginxNettyServerHandler.class);
private final NginxConfig nginxConfig;
public NginxNettyServerHandler(NginxConfig nginxConfig) {
this.nginxConfig = nginxConfig;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String requestString = new String(bytes, nginxConfig.getCharset());
logger.info("[Nginx] channelRead requestString={}", requestString);
// 请求体
final NginxRequestConvertor requestConvertor = nginxConfig.getNginxRequestConvertor();
NginxRequestInfoBo nginxRequestInfoBo = requestConvertor.convert(requestString, nginxConfig);
// 分发
final NginxRequestDispatch requestDispatch = nginxConfig.getNginxRequestDispatch();
String respText = requestDispatch.dispatch(nginxRequestInfoBo, nginxConfig);
// 结果响应
ByteBuf responseBuf = Unpooled.copiedBuffer(respText.getBytes());
ctx.writeAndFlush(responseBuf)
.addListener(ChannelFutureListener.CLOSE); // Close the channel after sending the response
logger.info("[Nginx] channelRead writeAndFlush DONE");
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.error("[Nginx] exceptionCaught", cause);
ctx.close();
}
}
其中请求的解析为对象,便于后续开发中使用。
分发处理只是加了一层抽象,整体实现和上一节类似。
感兴趣可以阅读源码。
小结
本节我们使用 netty 大幅度提升一下响应性能。
到这里我们实现了一个简单的 http 服务器,当然这是远远不够的。
后续我们会继续一起实现更多的 nginx 特性。
我是老马,期待与你的下次重逢。
开源地址
为了便于大家学习,已经将 nginx 开源