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

前言

大家好,我是老马。很高兴遇到你。

我们希望实现最简单的 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 已经帮我们封装好了。

核心代码

启动类

我们对启动类调整如下:

/**
 * netty 实现
 *
 * @author 老马啸西风
 * @since 0.2.0
 */
public class NginxServerNetty implements INginxServer {

    //basic ...

    @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 {
                            ChannelPipeline p = ch.pipeline();

                            p.addLast(new HttpRequestDecoder()); // 请求消息解码器
                            p.addLast(new HttpObjectAggregator(65536)); // 目的是将多个消息转换为单一的request或者response对象
                            p.addLast(new HttpResponseEncoder()); // 响应解码器
                            p.addLast(new ChunkedWriteHandler()); // 目的是支持异步大文件传输
                            // 业务逻辑
                            p.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) {
            // 省略...
        }
    }

}

NginxNettyServerHandler 业务逻辑

这个类可以变得非常简单

/**
 * netty 处理类
 * @author 老马啸西风
 * @since 0.2.0
 */
public class NginxNettyServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    //...

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        logger.info("[Nginx] channelRead writeAndFlush start request={}", request);

        // 分发
        final NginxRequestDispatch requestDispatch = nginxConfig.getNginxRequestDispatch();
        FullHttpResponse response = requestDispatch.dispatch(request, nginxConfig);

        // 结果响应
        ChannelFuture lastContentFuture = ctx.writeAndFlush(response);
        //如果不支持keep-Alive,服务器端主动关闭请求
        if (!HttpUtil.isKeepAlive(request)) {
            lastContentFuture.addListener(ChannelFutureListener.CLOSE);
        }
        logger.info("[Nginx] channelRead writeAndFlush DONE response={}", response);
    }

    @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();
    }

}

分发处理

分发处理的逻辑,主要是构建响应内容。

我们先实现最基本的能力:

    /**
     * 内容的分发处理
     *
     * @param requestInfoBo 请求
     * @param nginxConfig   配置
     * @return 结果
     * @author 老马啸西风
     */
    public FullHttpResponse dispatch(final FullHttpRequest requestInfoBo, final NginxConfig nginxConfig) {
        // 消息解析不正确
        /*如果无法解码400*/
        if (!requestInfoBo.decoderResult().isSuccess()) {
            log.warn("[Nginx] base request for http={}", requestInfoBo);
            return buildCommentResp(null, HttpResponseStatus.BAD_REQUEST, requestInfoBo, nginxConfig);
        }

        final String basicPath = nginxConfig.getHttpServerRoot();
        final String path = requestInfoBo.uri();

        boolean isRootPath = isRootPath(requestInfoBo, nginxConfig);
        // 根路径
        if(isRootPath) {
            log.info("[Nginx] current req meet root path");
            String indexContent = nginxConfig.getNginxIndexContent().getContent(nginxConfig);
            return buildCommentResp(indexContent, HttpResponseStatus.OK, requestInfoBo, nginxConfig);
        }

        // other
        String fullPath = FileUtil.buildFullPath(basicPath, path);
        if(FileUtil.exists(fullPath)) {
            String fileContent = FileUtil.getFileContent(fullPath);
            return buildCommentResp(fileContent, HttpResponseStatus.OK, requestInfoBo, nginxConfig);
        }  else {
            return buildCommentResp(null, HttpResponseStatus.NOT_FOUND, requestInfoBo, nginxConfig);
        }
    }

核心逻辑:

1)如果请求体解析失败,直接返回。

2)根路径,则返回 index 内容

3)否则解析处理文件内容,不存在则返回 404

resp 构建的方法暂时简单实现如下,后续我们会持续改进

    /**
     * String format = "HTTP/1.1 200 OK\r\n" +
     *                 "Content-Type: text/plain\r\n" +
     *                 "\r\n" +
     *                 "%s";
     *
     * @param rawText 原始内容
     * @param status 结果枚举
     * @param request 请求内容
     * @param nginxConfig 配置
     * @return 结果
     * @author 老马啸西风
     */
    protected FullHttpResponse buildCommentResp(String rawText,
                                            final HttpResponseStatus status,
                                            final FullHttpRequest request,
                                            final NginxConfig nginxConfig) {
        String defaultContent = status.toString();
        if(StringUtil.isNotEmpty(rawText)) {
            defaultContent = rawText;
        }

        // 构造响应
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                status, Unpooled.copiedBuffer(defaultContent, CharsetUtil.UTF_8));
        // 头信息
        // TODO: 根据文件变化
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
        //如果request中有KEEP ALIVE信息
        if (HttpUtil.isKeepAlive(request)) {
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        }

        return response;
    }

小结

本节我们使用 netty 简化出入参的处理。

但是响应的构建还不够完善,我们下一节来一起优化一下响应的处理。

我是老马,期待与你的下次重逢。

开源地址

为了便于大家学习,已经将 nginx 开源

https://github.com/houbb/nginx4j

相关推荐

  1. 实现 tomcat-05-servlet 处理支持

    2024-06-08 04:50:05       9 阅读
  2. 实现 nginx-09-compress http 文件压缩

    2024-06-08 04:50:05       9 阅读
  3. 实现 nginx-21-modules 模块

    2024-06-08 04:50:05       9 阅读
  4. 实现 tomcat-03-基本的 socket 实现

    2024-06-08 04:50:05       12 阅读
  5. 实现 apache Tomcat-01-入门介绍

    2024-06-08 04:50:05       10 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-08 04:50:05       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-08 04:50:05       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-08 04:50:05       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-08 04:50:05       18 阅读

热门阅读

  1. 进位(bit)

    2024-06-08 04:50:05       8 阅读
  2. 如何在Python中创建和使用自定义模块

    2024-06-08 04:50:05       11 阅读
  3. 局域网、城域网、广域网的ip

    2024-06-08 04:50:05       9 阅读
  4. SpringMVC:@RequestMapping注解

    2024-06-08 04:50:05       8 阅读
  5. 【嵌入式 - 关于MCU的内存分配】

    2024-06-08 04:50:05       9 阅读
  6. Android面试题汇总-Handler

    2024-06-08 04:50:05       11 阅读
  7. Mybatis面试系列五

    2024-06-08 04:50:05       9 阅读
  8. Vue3响应式基础——ref()和reactive()

    2024-06-08 04:50:05       7 阅读
  9. Vue封装localStorage设置过期时间

    2024-06-08 04:50:05       8 阅读
  10. 使用 Ant Design Vue 实现动态表头与数据填充

    2024-06-08 04:50:05       9 阅读
  11. learn-vue中template根节点元素Div

    2024-06-08 04:50:05       8 阅读
  12. 2024全国高考作文题解读(文心一言 4.0版本)

    2024-06-08 04:50:05       11 阅读