前言
大家好,我是老馬。很高興遇到你。
我們希望實現最簡單的 http 服務資訊,可以處理靜態檔案。
如果你想知道 servlet 如何處理的,可以參考我的另一個專案:
手寫從零實現簡易版 tomcat minicat
netty 相關
如果你對 netty 不是很熟悉,可以讀一下
Netty 權威指南-01-BIO 案例
Netty 權威指南-02-NIO 案例
Netty 權威指南-03-AIO 案例
Netty 權威指南-04-為什麼選擇 Netty?Netty 入門教程
手寫 nginx 系列
如果你對 nginx 原理感興趣,可以閱讀:
從零手寫實現 nginx-01-為什麼不能有 java 版本的 nginx?
從零手寫實現 nginx-02-nginx 的核心能力
從零手寫實現 nginx-03-nginx 基於 Netty 實現
從零手寫實現 nginx-04-基於 netty http 出入參最佳化處理
從零手寫實現 nginx-05-MIME型別(Multipurpose Internet Mail Extensions,多用途網際網路郵件擴充套件型別)
從零手寫實現 nginx-06-資料夾自動索引
從零手寫實現 nginx-07-大檔案下載
從零手寫實現 nginx-08-範圍查詢
從零手寫實現 nginx-09-檔案壓縮
從零手寫實現 nginx-10-sendfile 零複製
從零手寫實現 nginx-11-file+range 合併
從零手寫實現 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 的配置我們暫時使用預設值,後續可以考慮可以讓使用者自定義。
/**
* 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 件事
-
解析請求類
-
根據請求獲取對應的內容
-
返回響應內容
/**
* 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 開源
https://github.com/houbb/nginx4j