从零手写实现 nginx-04-基于 netty http 出入参优化处理

打印 上一主题 下一主题

主题 939|帖子 939|积分 2817

媒介

大家好,我是老马。很高兴碰到你。
我们希望实现最简朴的 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
媒介

我们上一篇文章中,利用 netty 优化我们的 io 模子。
对于哀求和响应是基于自己的代码封装实现的。
但是 http 协议本身比力复杂,自己实现起来要泯灭大量的时间。
那么,有没有现成的实现呢?
答案是 netty 已经帮我们封装好了。
焦点代码

启动类

我们对启动类调整如下:
  1. /**
  2. * netty 实现
  3. *
  4. * @author 老马啸西风
  5. * @since 0.2.0
  6. */
  7. public class NginxServerNetty implements INginxServer {
  8.     //basic ...
  9.     @Override
  10.     public void start() {
  11.         // 服务器监听的端口号
  12.         String host = InnerNetUtil.getHost();
  13.         int port = nginxConfig.getHttpServerListen();
  14.         EventLoopGroup bossGroup = new NioEventLoopGroup();
  15.         //worker 线程池的数量默认为 CPU 核心数的两倍
  16.         EventLoopGroup workerGroup = new NioEventLoopGroup();
  17.         try {
  18.             ServerBootstrap serverBootstrap = new ServerBootstrap();
  19.             serverBootstrap.group(bossGroup, workerGroup)
  20.                     .channel(NioServerSocketChannel.class)
  21.                     .childHandler(new ChannelInitializer<SocketChannel>() {
  22.                         @Override
  23.                         protected void initChannel(SocketChannel ch) throws Exception {
  24.                             ChannelPipeline p = ch.pipeline();
  25.                             p.addLast(new HttpRequestDecoder()); // 请求消息解码器
  26.                             p.addLast(new HttpObjectAggregator(65536)); // 目的是将多个消息转换为单一的request或者response对象
  27.                             p.addLast(new HttpResponseEncoder()); // 响应解码器
  28.                             p.addLast(new ChunkedWriteHandler()); // 目的是支持异步大文件传输
  29.                             // 业务逻辑
  30.                             p.addLast(new NginxNettyServerHandler(nginxConfig));
  31.                         }
  32.                     })
  33.                     .option(ChannelOption.SO_BACKLOG, 128)
  34.                     .childOption(ChannelOption.SO_KEEPALIVE, true);
  35.             // Bind and start to accept incoming connections.
  36.             ChannelFuture future = serverBootstrap.bind(port).sync();
  37.             log.info("[Nginx4j] listen on http://{}:{}", host, port);
  38.             // Wait until the server socket is closed.
  39.             future.channel().closeFuture().sync();
  40.         } catch (InterruptedException e) {
  41.             // 省略...
  42.         }
  43.     }
  44. }
复制代码
NginxNettyServerHandler 业务逻辑

这个类可以变得非常简朴
  1. /**
  2. * netty 处理类
  3. * @author 老马啸西风
  4. * @since 0.2.0
  5. */
  6. public class NginxNettyServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
  7.     //...
  8.     @Override
  9.     protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
  10.         logger.info("[Nginx] channelRead writeAndFlush start request={}", request);
  11.         // 分发
  12.         final NginxRequestDispatch requestDispatch = nginxConfig.getNginxRequestDispatch();
  13.         FullHttpResponse response = requestDispatch.dispatch(request, nginxConfig);
  14.         // 结果响应
  15.         ChannelFuture lastContentFuture = ctx.writeAndFlush(response);
  16.         //如果不支持keep-Alive,服务器端主动关闭请求
  17.         if (!HttpUtil.isKeepAlive(request)) {
  18.             lastContentFuture.addListener(ChannelFutureListener.CLOSE);
  19.         }
  20.         logger.info("[Nginx] channelRead writeAndFlush DONE response={}", response);
  21.     }
  22.     @Override
  23.     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
  24.         ctx.flush();
  25.     }
  26.     @Override
  27.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  28.         logger.error("[Nginx] exceptionCaught", cause);
  29.         ctx.close();
  30.     }
  31. }
复制代码
分发处理

分发处理的逻辑,主要是构建响应内容。
我们先实现最根本的能力:
  1.     /**
  2.      * 内容的分发处理
  3.      *
  4.      * @param requestInfoBo 请求
  5.      * @param nginxConfig   配置
  6.      * @return 结果
  7.      * @author 老马啸西风
  8.      */
  9.     public FullHttpResponse dispatch(final FullHttpRequest requestInfoBo, final NginxConfig nginxConfig) {
  10.         // 消息解析不正确
  11.         /*如果无法解码400*/
  12.         if (!requestInfoBo.decoderResult().isSuccess()) {
  13.             log.warn("[Nginx] base request for http={}", requestInfoBo);
  14.             return buildCommentResp(null, HttpResponseStatus.BAD_REQUEST, requestInfoBo, nginxConfig);
  15.         }
  16.         final String basicPath = nginxConfig.getHttpServerRoot();
  17.         final String path = requestInfoBo.uri();
  18.         boolean isRootPath = isRootPath(requestInfoBo, nginxConfig);
  19.         // 根路径
  20.         if(isRootPath) {
  21.             log.info("[Nginx] current req meet root path");
  22.             String indexContent = nginxConfig.getNginxIndexContent().getContent(nginxConfig);
  23.             return buildCommentResp(indexContent, HttpResponseStatus.OK, requestInfoBo, nginxConfig);
  24.         }
  25.         // other
  26.         String fullPath = FileUtil.buildFullPath(basicPath, path);
  27.         if(FileUtil.exists(fullPath)) {
  28.             String fileContent = FileUtil.getFileContent(fullPath);
  29.             return buildCommentResp(fileContent, HttpResponseStatus.OK, requestInfoBo, nginxConfig);
  30.         }  else {
  31.             return buildCommentResp(null, HttpResponseStatus.NOT_FOUND, requestInfoBo, nginxConfig);
  32.         }
  33.     }
复制代码
焦点逻辑:
1)如果哀求体解析失败,直接返回。
2)根路径,则返回 index 内容
3)否则解析处理文件内容,不存在则返回 404
resp 构建的方法暂时简朴实现如下,后续我们会持续改进
  1.     /**
  2.      * String format = "HTTP/1.1 200 OK\r\n" +
  3.      *                 "Content-Type: text/plain\r\n" +
  4.      *                 "\r\n" +
  5.      *                 "%s";
  6.      *
  7.      * @param rawText 原始内容
  8.      * @param status 结果枚举
  9.      * @param request 请求内容
  10.      * @param nginxConfig 配置
  11.      * @return 结果
  12.      * @author 老马啸西风
  13.      */
  14.     protected FullHttpResponse buildCommentResp(String rawText,
  15.                                             final HttpResponseStatus status,
  16.                                             final FullHttpRequest request,
  17.                                             final NginxConfig nginxConfig) {
  18.         String defaultContent = status.toString();
  19.         if(StringUtil.isNotEmpty(rawText)) {
  20.             defaultContent = rawText;
  21.         }
  22.         // 构造响应
  23.         FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
  24.                 status, Unpooled.copiedBuffer(defaultContent, CharsetUtil.UTF_8));
  25.         // 头信息
  26.         // TODO: 根据文件变化
  27.         response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
  28.         response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
  29.         //如果request中有KEEP ALIVE信息
  30.         if (HttpUtil.isKeepAlive(request)) {
  31.             response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
  32.         }
  33.         return response;
  34.     }
复制代码
小结

本节我们利用 netty 简化出入参的处理。
但是响应的构建还不够完善,我们下一节来一起优化一下响应的处理。
我是老马,期待与你的下次重逢。
开源所在

为了便于大家学习,已经将 nginx 开源
   https://github.com/houbb/nginx4j

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

曹旭辉

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表