基于Netty4的HttpServer和HttpClient的简单实现

本文介绍如何使用Netty框架实现HTTP服务器与客户端的基本交互流程。包括HTTP请求与响应的解析、编码与解码等关键步骤,并提供了一个完整的代码示例。

Netty的主页:http://netty.io/index.html

使用的Netty的版本:netty-4.0.23.Final.tar.bz2 ‐ 15-Aug-2014 (Stable, Recommended)

Http 消息格式:

Http request:

Method path-to-resource HTTPVersion-number
Header-name-1: value1
Header-name-2: value2

Optional request body

Http response:

HTTP/Version-number response-code response-phrase 
Header-name-1: value1
Header-name-2: value2

Optional response body

实现一个简单的Http请求及响应过程:

1、Client向Server发送http请求。

2、Server端对http请求进行解析。

3、Server端向client发送http响应。

4、Client对http响应进行解析。

Netty中Http request消息格式:

Netty中Http response消息格式:

代码实例:

Http Server:

package com.netty.test;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture; 
import io.netty.channel.ChannelInitializer; 
import io.netty.channel.ChannelOption; 
import io.netty.channel.EventLoopGroup; 
import io.netty.channel.nio.NioEventLoopGroup; 
import io.netty.channel.socket.SocketChannel; 
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpRequestDecoder; 
import io.netty.handler.codec.http.HttpResponseEncoder; 

public class HttpServer {

    private static Log log = LogFactory.getLog(HttpServer.class);
    
    public void start(int port) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                                @Override
                                public void initChannel(SocketChannel ch) throws Exception {
                                    // server端发送的是httpResponse,所以要使用HttpResponseEncoder进行编码
                                    ch.pipeline().addLast(new HttpResponseEncoder());
                                    // server端接收到的是httpRequest,所以要使用HttpRequestDecoder进行解码
                                    ch.pipeline().addLast(new HttpRequestDecoder());
                                    ch.pipeline().addLast(new HttpServerInboundHandler());
                                }
                            }).option(ChannelOption.SO_BACKLOG, 128) 
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture f = b.bind(port).sync();

            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        HttpServer server = new HttpServer();
        log.info("Http Server listening on 8844 ...");
        server.start(8844);
    }
}

响应请求的HttpServerInboundHandler:

package com.netty.test;

import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpHeaders.Values;
import io.netty.handler.codec.http.HttpRequest;

public class HttpServerInboundHandler extends ChannelInboundHandlerAdapter {

    private static Log log = LogFactory.getLog(HttpServerInboundHandler.class);

    private HttpRequest request;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        if (msg instanceof HttpRequest) {
            request = (HttpRequest) msg;

            String uri = request.getUri();
            System.out.println("Uri:" + uri);
        }
        if (msg instanceof HttpContent) {
            HttpContent content = (HttpContent) msg;
            ByteBuf buf = content.content();
            System.out.println(buf.toString(io.netty.util.CharsetUtil.UTF_8));
            buf.release();
String res
= "I am OK"; FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(res.getBytes("UTF-8"))); response.headers().set(CONTENT_TYPE, "text/plain"); response.headers().set(CONTENT_LENGTH, response.content().readableBytes()); if (HttpHeaders.isKeepAlive(request)) { response.headers().set(CONNECTION, Values.KEEP_ALIVE); } ctx.write(response); ctx.flush(); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { log.error(cause.getMessage()); ctx.close(); } }

Http Client:

package com.netty.test;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpResponseDecoder;
import io.netty.handler.codec.http.HttpVersion;

import java.net.URI;

public class HttpClient {
    public void connect(String host, int port) throws Exception {
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup);
            b.channel(NioSocketChannel.class);
            b.option(ChannelOption.SO_KEEPALIVE, true);
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    // 客户端接收到的是httpResponse响应,所以要使用HttpResponseDecoder进行解码
                    ch.pipeline().addLast(new HttpResponseDecoder());
                    // 客户端发送的是httprequest,所以要使用HttpRequestEncoder进行编码
                    ch.pipeline().addLast(new HttpRequestEncoder());
                    ch.pipeline().addLast(new HttpClientInboundHandler());
                }
            });

            // Start the client.
            ChannelFuture f = b.connect(host, port).sync();

            URI uri = new URI("http://127.0.0.1:8844");
            String msg = "Are you ok?";
            DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
                    uri.toASCIIString(), Unpooled.wrappedBuffer(msg.getBytes("UTF-8")));

            // 构建http请求
            request.headers().set(HttpHeaders.Names.HOST, host);
            request.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
            request.headers().set(HttpHeaders.Names.CONTENT_LENGTH, request.content().readableBytes());
            // 发送http请求
            f.channel().write(request);
            f.channel().flush();
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
        }

    }

    public static void main(String[] args) throws Exception {
        HttpClient client = new HttpClient();
        client.connect("127.0.0.1", 8844);
    }
}

处理Server响应的HttpClientInboundHandler:

package com.netty.test;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponse;

public class HttpClientInboundHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof HttpResponse) 
        {
            HttpResponse response = (HttpResponse) msg;
            System.out.println("CONTENT_TYPE:" + response.headers().get(HttpHeaders.Names.CONTENT_TYPE));
        }
        if(msg instanceof HttpContent)
        {
            HttpContent content = (HttpContent)msg;
            ByteBuf buf = content.content();
            System.out.println(buf.toString(io.netty.util.CharsetUtil.UTF_8));
            buf.release();
        }
    }
}

 

参考代码:https://github.com/luxiaoxun/Code4Java

 

参考:

https://github.com/netty/netty

 

<think> 首先,用户的问题是关于“reactor.netty.http.server.http报错解决方案”。用户提到了在开发网关系统时遇到错误,引用了一些日志异常信息。用户还提供了三个引用,但这些都是关于连接错误类未找到的错误,似乎与直接解决“reactor.netty.http.server.http报错”相关但不完全匹配。 用户的需求是:查找reactor.netty.http.server.http报错的解决方案。用户没有提供具体的错误信息,所以我需要基于常见的错误提供一般性建议。 回顾用户提供的引用: - 引用[1]:警告日志,关于连接错误。 - 引用[2]:详细异常,提到了“Connection prematurely closed BEFORE response”,发生在网关系统中配置rabbitmq时。 - 引用[3]:NoClassDefFoundError,关于reactor.ipc.netty.http.client.HttpClient。 用户说“上述引用仅供参考”,所以我不能直接依赖这些来解决当前问题。用户现在的问题是针对“reactor.netty.http.server.http报错”,这可能与Reactor Netty HTTP服务器的错误相关。 Reactor Netty是一个用于构建反应式网络应用的库。常见的错误可能包括配置问题、依赖问题、资源不足等。 作为AI,我应该: - 提供结构清晰的解决方案。 - 使用中文回答。 - 遵循系统指令:行内数学表达式用$...$,独立公式用$$...$$(但这个问题可能不涉及数学),确保LaTeX语法正确。 - 生成相关问题。 - 在回答中引用段落时添加引用标识,如[^1]。 - 回答结构清晰,逐步解决问题。 我的回答应分为几个部分: 1. **理解问题**:解释reactor.netty.http.server.http报错的常见原因。 2. **解决方案步骤**:提供逐步的解决建议。 3. **代码示例**:如果有必要,提供代码片段。 4. **相关问题**:生成相关问题。 由于用户没有提供具体的错误日志,我需要给出一般性建议。如果用户提供了更多细节,我可以更针对性地回答。 常见原因: - **依赖问题**:Maven或Gradle依赖不正确。 - **配置错误**:服务器配置不当,如端口冲突、SSL问题。 - **资源泄漏**:连接未正确关闭,导致资源耗尽。 - **异常处理**:未处理的异常导致服务器崩溃。 - **版本冲突**:不同库版本不兼容。 解决方案: 1. **检查依赖**:确保使用正确的Reactor Netty版本。 2. **查看日志**:分析错误日志以识别根本原因。 3. **配置优化**:调整HTTP服务器设置。 4. **代码审查**:检查服务器启动请求处理代码。 5. **测试环境**:在开发环境中重现问题。 生成相关问题: - 如何解决Reactor Netty中的连接错误? - Reactor Netty HTTP服务器的最佳实践是什么? - 如何处理Reactor Netty中的资源泄漏? 在回答中,如果需要引用用户提供的信息,添加引用标识。但用户的问题中没有直接引用段落,所以可能不需要添加。 最终回答结构: - 开头:简要介绍问题。 - 主体:分步骤提供解决方案。 - 结尾:生成相关问题。 </think> 以下针对“reactor.netty.http.server.http报错”提供解决方案。Reactor Netty是一个基于反应式编程的网络库(常用于Spring WebFlux),HTTP服务器报错通常由依赖冲突、配置错误或资源问题引起。基于常见实践,我将分步骤解释解决方案。由于用户未提供具体错误日志,我将覆盖典型场景;如果问题持续,请补充错误细节以便更精准分析。 ### 一、问题常见原因分析 1. **依赖问题(最常见)**: - Reactor Netty版本与其他库(如Spring Boot)不兼容,导致类加载失败或方法缺失。 - 示例错误:`java.lang.NoClassDefFoundError` 或 `MethodNotFoundException`,如引用[3]所示[^3]。 2. **配置错误**: - HTTP服务器端口冲突、SSL证书无效或超时设置不合理。 - 示例错误:连接过早关闭(如引用[2]中的 `Connection prematurely closed BEFORE response`)[^2]。 3. **资源泄漏或高负载**: - 未释放的连接或线程池耗尽,引发 `ReactorNetty` 内部异常。 4. **代码逻辑缺陷**: - 请求处理逻辑未正确处理异常,导致服务器崩溃。 ### 二、逐步解决方案 #### 步骤1:检查并修复依赖(优先级最高) - **验证依赖版本**:确保 `reactor-netty` 与 Spring Boot 版本兼容。推荐使用 Spring Boot 官方兼容矩阵: - Spring Boot 2.x:使用 `reactor-netty` 0.9.x 或 1.0.x。 - Spring Boot 3.x:使用 `reactor-netty` 1.1.x 或更高。 在 `pom.xml` (Maven) 或 `build.gradle` (Gradle) 中显式指定版本: ```xml <!-- Maven 示例 --> <dependency> <groupId>io.projectreactor.netty</groupId> <artifactId>reactor-netty-http</artifactId> <version>1.1.12</version> <!-- 根据Spring Boot版本调整 --> </dependency> ``` 运行 `mvn dependency:tree` 或 `gradle dependencies` 检查冲突,排除旧版本依赖。 - **常见修复**:若遇到类未找到错误(如引用[3]),添加缺失依赖: ```xml <dependency> <groupId>io.projectreactor.netty</groupId> <artifactId>reactor-netty-core</artifactId> <version>1.1.12</version> </dependency> ``` #### 步骤2:优化HTTP服务器配置 - **调整超时连接池**:在 `application.yml` 中增加配置以避免连接过早关闭: ```yaml server: reactor: netty: connection-timeout: 5000ms # 增加连接超时 max-connections: 1000 # 提高最大连接数 max-idle-time: 30000ms # 空闲连接超时 ``` 引用[2]中的错误通常由超时设置过短引起[^2]。 - **端口SSL检查**:确保端口未被占用(如8080),并验证SSL证书(如果使用HTTPS): ```java // 代码示例:启动HTTP服务器时检查端口 HttpServer.create() .port(8080) .bindNow(); ``` #### 步骤3:诊断资源泄漏 - **启用详细日志**:在 `logback.xml` 中添加Reactor Netty日志级别: ```xml <logger name="reactor.netty" level="DEBUG" /> ``` 分析日志中的 `onError` 事件,定位资源未释放的代码位置。 - **使用监控工具**:集成Micrometer监控连接池: ```java // 在Spring Boot启动类中添加 @Bean public NettyServerCustomizer nettyCustomizer() { return server -> server.metrics(true, uri -> true); } ``` 查看指标如 `reactor.netty.connection.provider.total.connections`。 #### 步骤4:代码审查与异常处理 - **强化错误处理**:在请求处理链中添加全局异常捕获: ```java // Spring WebFlux 示例 @Bean public WebExceptionHandler customExceptionHandler() { return (exchange, ex) -> { if (ex instanceof PrematureCloseException) { // 处理连接关闭错误 return exchange.getResponse().setComplete(); } return Mono.error(ex); }; } ``` 避免未处理异常导致服务器不稳定。 - **压力测试**:使用工具(如JMeter)模拟高并发,复现错误并调整参数。 ### 三、验证解决方案 1. 在本地运行测试:确保无依赖冲突。 2. 逐步部署到开发环境:监控日志是否出现 `reactor.netty.http.server` 相关错误。 3. 参考官方文档:[Reactor Netty Troubleshooting](https://projectreactor.io/docs/netty/release/reference/index.html#troubleshooting)。 如果上述步骤无效,请提供具体错误日志(如堆栈跟踪),我将进一步分析。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值