netty可以支持多种协议, 其中就支持HTTP协议, 可以用来做HTTP服务 (详细说明)
Netty 提供的 ChannelHandler是怎样允许您使用 HTTP 和 HTTPS 而无需编写自己的编解码器。
HTTP Decoder, Encoder 和 Codec
1.0 编写服务端
@Slf4j
public class NettyHttpService {
public static void main(String[] args) {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(2); // 接收连接线程
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
// 使用自定义初始化处理器
.childHandler(new HttpPipelineInitializer(true,new NettyHttpServerHandler()));
ChannelFuture channelFuture = serverBootstrap.bind(8888).sync();
log.info("NettyHttpService-serverBootstrap, 服务端启动成功!");
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
1.1 编写自定义初始化处理器
/**
* @Author: ZhiHao
* @Date: 2022/2/21 17:43
* @Description: http管道初始值设定项
* @Versions 1.0
**/
public class HttpPipelineInitializer extends ChannelInitializer<SocketChannel> {
private boolean isService;
private ChannelHandler channelHandler;
public HttpPipelineInitializer(boolean isService, ChannelHandler channelHandler) {
// 服务端/客户端不同构造, 添加处理器顺序
this.isService = isService;
this.channelHandler = channelHandler;
}
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
if (isService){
pipeline.addLast(new HttpServerCodec()); // HttpRequestDecoder与HttpResponseEncoder组合, 默认初始化参数
}else {
pipeline.addLast(new HttpClientCodec()); // Decoder与Encoder组合, 默认初始化参数
}
// 添加自定义处理器
pipeline.addLast(channelHandler);
}
}
1.1.1 HTTP消息聚合
ChannelPipeline 中的初始化之后,你能够对不同 HttpObject 消息进行操作。但由于 HTTP 请求和响应可以由许多部分组合而成,你需要聚合他们形成完整的消息。为了消除这种繁琐任务, Netty 提供了一个聚合器,合并消息部件到 FullHttpRequest 和 FullHttpResponse 消息。这样您总是能够看到完整的消息内容。
这个操作有一个轻微的成本,消息段需要缓冲,直到完全可以将消息转发到下一个 ChannelInboundHandler 管道。但好处是,你不必担心消息碎片。
实现自动聚合只需添加另一个
HttpObjectAggregator
到 ChannelPipeline。
// HttpPipelineInitializer-其他代码省略
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
if (isService){
pipeline.addLast(new HttpServerCodec());
}else {
pipeline.addLast(new HttpClientCodec());
}
//使用最大消息值是 512kb
pipeline.addLast(new HttpObjectAggregator(512 * 1024));
pipeline.addLast(channelHandler);
}
1.1.2 HTTP 压缩
使用 HTTP 时建议压缩数据以减少传输流量,压缩数据会增加 CPU 负载,现在的硬件设施都很强大,大多数时候压缩数据时一个好主意。Netty 支持“gzip”和“deflate”,为此提供了两个 ChannelHandler 实现分别用于压缩和解压。
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
if (isService){
pipeline.addLast(new HttpServerCodec());
// 用于压缩来自 client 支持的 HttpContentCompressor
pipeline.addLast(new HttpContentCompressor());
}else {
pipeline.addLast(new HttpClientCodec());
// 用于处理来自服务器的压缩的内容
pipeline.addLast(new HttpContentDecompressor());
}
pipeline.addLast(new HttpObjectAggregator(512 * 1024));
pipeline.addLast(channelHandler);
}
1.2 编写自定义服务端处理器
/**
* @Author: ZhiHao
* @Date: 2022/2/16 16:28
* @Description: ChannelInboundHandlerAdapter是ChannelInboundHandler实现的抽象基类,它提供了所有方法的实现。
* 这个实现只是将操作转发给ChannelPipeline中的下一个ChannelHandler。
* 子类可以重写方法实现来更改这一点。 请注意,在channelRead(ChannelHandlerContext,Object)方法自动返回后,
* 不会释放消息。如果您正在寻找自动释放接收到的消息的ChannelInboundHandler实现,请参阅SimpleChannelInboundHandler。
* @Versions 1.0
**/
@Slf4j
// SimpleChannelInboundHandler继承于ChannelInboundHandlerAdapter, 并且会自动释放消息
// HttpObject 客户端和服务器端相互通讯的数据被封装成 HttpObject
@ChannelHandler.Sharable
public class NettyHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
/**
* 内部做了 channelRead方法执行后 => 调用子类重写的channelRead0
*
* @param ctx
* @param msg
* @author: ZhiHao
* @date: 2022/2/21
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
// 判断是否是一个HTTP请求
if (msg instanceof FullHttpRequest) {
log.info("NettyHttpServerHandler-channelRead0-1, msg类型:{}", msg.getClass());
FullHttpRequest request = (FullHttpRequest) msg;
log.info("NettyHttpServerHandler-channelRead0-2, 请求路径:{}", request.uri());
log.info("NettyHttpServerHandler-channelRead0-3, 请求方式:{}", request.method().name());
log.info("NettyHttpServerHandler-channelRead0-4, 请求头:{}", request.headers().toString());
ByteBuf byteBufContent = request.content();
int length = byteBufContent.readableBytes();
byte[] bytes = new byte[length];
byteBufContent.readBytes(bytes,0, length);
String content = new String(bytes);
log.info("NettyHttpServerHandler-channelRead0-5, 请求参数:{}", content);
ByteBuf byteBuf = Unpooled.copiedBuffer("客户端你好", CharsetUtil.UTF_8);
// 响应数据给客户端
FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.OK, byteBuf);
fullHttpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
fullHttpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
ctx.writeAndFlush(fullHttpResponse);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("NettyHttpServerHandler-exceptionCaught, 出现异常:",cause);
ctx.close();
}
}
@ChannelHandler.Sharable
用于标注一个channel handler可以被多个channel安全地共享。没有加上注解, 多个客户端连接会出现异常的:
.childHandler(new HttpPipelineInitializer(true,new NettyHttpServerHandler()));
因为只构建了一次HttpPipelineInitializer
, 里面的处理器也是只有一个, 所以每次新来的连接, 使用的都是同一个NettyHttpServerHandler
处理器, 然后会报出这个异常:
io.netty.channel.ChannelPipelineException: com.zhihao.netty.http.NettyHttpServerHandler is not a @Sharable handler, so can't be added or removed multiple times.
解决方案: 加上注解
@ChannelHandler.Sharable
或者 每次都new一个新的处理器 :
// .childHandler(new HttpPipelineInitializertwo(true,"service"));
public class HttpPipelineInitializertwo extends ChannelInitializer<SocketChannel> {
private boolean isService;
private String channelHandlerName;
public HttpPipelineInitializertwo(boolean isService, String channelHandlerName) {
// 服务端/客户端不同构造, 添加处理器顺序
this.isService = isService;
this.channelHandlerName = channelHandlerName;
}
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// ... 其他代码省略
// 添加自定义处理器
if ("service".equals(channelHandlerName)){
pipeline.addLast(new NettyHttpServerHandler());
}
if ("client".equals(channelHandlerName)){
pipeline.addLast(new NettyHttpClientHandler());
}
}
}
2.0 使用postman测试
一般使用netty做HTTP服务端, 不会使用netty做客户端的
- get请求
17:06:51.377 [main] INFO com.zhihao.netty.http.NettyHttpService - NettyHttpService-serverBootstrap, 服务端启动成功!
17:06:57.584 [nioEventLoopGroup-3-2] INFO com.zhihao.netty.http.NettyHttpServerHandler - NettyHttpServerHandler-channelRead0-1, msg类型:class io.netty.handler.codec.http.HttpObjectAggregator$AggregatedFullHttpRequest
17:06:57.584 [nioEventLoopGroup-3-2] INFO com.zhihao.netty.http.NettyHttpServerHandler - NettyHttpServerHandler-channelRead0-2, 请求路径:/test/my?name=%E4%BD%A0%E5%A5%BD,%20%E4%B8%96%E7%95%8C
17:06:57.584 [nioEventLoopGroup-3-2] INFO com.zhihao.netty.http.NettyHttpServerHandler - NettyHttpServerHandler-channelRead0-3, 请求方式:GET
17:06:57.584 [nioEventLoopGroup-3-2] INFO com.zhihao.netty.http.NettyHttpServerHandler - NettyHttpServerHandler-channelRead0-4, 请求头:DefaultHttpHeaders[User-Agent: apifox/2.0.0-alpha.12 (https://www.apifox.cn), Accept: */*, Host: localhost:8888, Accept-Encoding: gzip, deflate, br, Connection: keep-alive, content-length: 0]
17:06:57.584 [nioEventLoopGroup-3-2] INFO com.zhihao.netty.http.NettyHttpServerHandler - NettyHttpServerHandler-channelRead0-5, 请求参数:
- post请求
17:14:56.527 [main] INFO com.zhihao.netty.http.NettyHttpService - NettyHttpService-serverBootstrap, 服务端启动成功!
17:15:51.480 [nioEventLoopGroup-3-2] INFO com.zhihao.netty.http.NettyHttpServerHandler - NettyHttpServerHandler-channelRead0-1, msg类型:class io.netty.handler.codec.http.HttpObjectAggregator$AggregatedFullHttpRequest
17:15:53.194 [nioEventLoopGroup-3-2] INFO com.zhihao.netty.http.NettyHttpServerHandler - NettyHttpServerHandler-channelRead0-2, 请求路径:/test/my
17:15:55.690 [nioEventLoopGroup-3-2] INFO com.zhihao.netty.http.NettyHttpServerHandler - NettyHttpServerHandler-channelRead0-3, 请求方式:POST
17:15:58.129 [nioEventLoopGroup-3-2] INFO com.zhihao.netty.http.NettyHttpServerHandler - NettyHttpServerHandler-channelRead0-4, 请求头:DefaultHttpHeaders[User-Agent: apifox/2.0.0-alpha.12 (https://www.apifox.cn), Content-Type: application/json, Accept: */*, Host: localhost:8888, Accept-Encoding: gzip, deflate, br, Connection: keep-alive, content-length: 37]
success
17:16:26.271 [nioEventLoopGroup-3-2] INFO com.zhihao.netty.http.NettyHttpServerHandler - NettyHttpServerHandler-channelRead0-5, 请求参数:{
"name" : "你好服务端!"
}
如果使用浏览器访问, 需要过滤掉无效的静态资源请求
扩展:
使用netty客户端请求netty服务端 (http)
客户端
@Slf4j
public class NettyHttpClient {
public static void main(String[] args) {
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
// 使用自定义初始化处理器
.handler(new HttpPipelineInitializer(false,new NettyHttpClientHandler()));
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1",8888).sync();
log.info("NettyHttpClient-bootstrap, 客户端连接成功!");
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
}
客户端处理器
@Slf4j
public class NettyHttpClientHandler extends SimpleChannelInboundHandler<HttpObject> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("NettyHttpClientHandler-channelActive, 通道激活:{}", ctx);
FullHttpMessage fullHttpMessage = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
HttpMethod.GET, "/test");
ByteBuf byteBuf = Unpooled.copiedBuffer("服务端你好", CharsetUtil.UTF_8);
fullHttpMessage = fullHttpMessage.replace(byteBuf);
fullHttpMessage.headers().add(HttpHeaderNames.HOST,"localhost");
fullHttpMessage.headers().add(HttpHeaderNames.CONNECTION,HttpHeaderValues.KEEP_ALIVE);
fullHttpMessage.headers().add(HttpHeaderNames.CONTENT_LENGTH,fullHttpMessage.content().readableBytes());
ctx.writeAndFlush(fullHttpMessage);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
// 判断是否是一个HTTP响应
if (msg instanceof FullHttpResponse) {
log.info("NettyHttpClientHandler-channelRead0-1, msg类型:{}", msg.getClass());
FullHttpResponse response = (FullHttpResponse) msg;
ByteBuf byteBuf = response.content();
int length = byteBuf.readableBytes();
byte[] bytes = new byte[length];
byteBuf.readBytes(bytes,0, length);
String content = new String(bytes);
log.info("NettyHttpClientHandler-channelRead0-3, 响应内容:{}", content);
log.info("NettyHttpClientHandler-channelRead0-3, 响应头:{}", response.headers());
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("NettyHttpClientHandler-exceptionCaught, 出现异常:",cause);
ctx.close();
}
}
1.1.3 使用 HTTPS
启用 HTTPS,只需添加 SslHandler
通过
SslContextBuilder.forClient().build()
建造SslContext
public class HttpsCodecInitializer extends ChannelInitializer<Channel> {
private final SslContext context;
private final boolean isService;
public HttpsCodecInitializer(SslContext context, boolean isService) {
this.context = context;
this.isService = isService;
}
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
SSLEngine engine = context.newEngine(ch.alloc());
// 添加 SslHandler 到 pipeline 来启用 HTTPS
pipeline.addFirst(new SslHandler(engine));
if (isService){
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpContentCompressor());
}else {
pipeline.addLast(new HttpClientCodec());
pipeline.addLast(new HttpContentDecompressor());
}
}
}
WebSocket (自行看网站)
1