Netty4.1 Http开发入门(一)服务端

开发了一个简单的Http Server,使用的是Netty 4.1.46.Final版本。

服务器类
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.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import lombok.extern.slf4j.Slf4j;

/**
 * 一个使用netty实现的简单的http服务器
 * HttpServerCodec是能同时处理入站和出站的编解码器
 * */
@Slf4j
public class SimpleHttpServer {

	private static final int _1M = 1024 * 1024;

	public static void main(String[] args) {
		log.info("启动SimpleHttpServer服务器...");
		EventLoopGroup boss = new NioEventLoopGroup();
		EventLoopGroup worker = new NioEventLoopGroup(2);
		try {
			ServerBootstrap bootstrap = new ServerBootstrap();
			bootstrap.option(ChannelOption.SO_BACKLOG, 1024).group(boss, worker).channel(NioServerSocketChannel.class)
					.childHandler(new ChannelInitializer<SocketChannel>() {

						@Override
						protected void initChannel(SocketChannel ch) {

							ch.pipeline().addLast("codec", new HttpServerCodec());
							ch.pipeline().addLast("aggregate", new HttpObjectAggregator(_1M));
							ch.pipeline().addLast("msg", new MyHttpMsgHandler());
						}

					});
			ChannelFuture future = bootstrap.bind(8080).sync();
			future.channel().closeFuture().sync();
		} catch (Exception e) {
			e.printStackTrace();
			log.error("http服务器错误:" + e.getMessage(), e);
		} finally {
			log.info("关闭http服务器");
			try {
				boss.shutdownGracefully().sync();
				worker.shutdownGracefully().sync();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
自定义Handler,用来处理Http Request并向客户端写Response
package com.wangan.netty_httpserver;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 用来处理http消息,包括请求与响应
 * 入站byte由HttpServerCodec转为请求消息,而出站响应同样由HttpServerCodec转为出站byte
 * */
@Slf4j
public class MyHttpMsgHandler extends SimpleChannelInboundHandler<HttpObject> {

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
		
		if (msg instanceof FullHttpRequest) {
			log.info("收到HTTP请求,聚合为FullHttpRequest");

			FullHttpRequest request = (FullHttpRequest) msg;
			log.info("HTTP Headers:{}", request.headers().toString());
			String requestId = request.headers().get("RequestID");
			log.info("Http RequestID:{}", requestId);
			String requestContent = request.content().toString(CharsetUtil.UTF_8);
			log.info("HTTP Content:{}", requestContent);

			ByteBuf responseContent = Unpooled.copiedBuffer("已收到,请求内容为" + requestContent, CharsetUtil.UTF_8);
			HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
					responseContent);
			if (requestId != null)
				response.headers().add("RequestID", requestId);
			ctx.write(response);
			ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
			future.addListener(ChannelFutureListener.CLOSE);//写完Response,关闭了Channel
			log.info("写回response");
		}

	}

}
关于HttpObjectAggregator

Http协议底层传输是基于TCP对数据进行字节传输的,传输的时候对端需要知道何时是一个完整的请求结束,一般会有如下几个方式:

  • 在Header里边的Content-Length:xxx属性里边声明传输请求的大小
  • 传输的数据比较大、或者没法预先知道传输数据的确切大小的时候,指定Header属性Transfer-Encoding: chunked采用所谓分块传输。每一个chunk第一行声明本次传输的数据块的长度、换行后面跟数据块的内容。一个完整请求最后被分为若干个chunk发送,最后一个chunk长度是0,标识请求结尾。
  • 如果没法预先获知并指定长度、也不支持chunked传输,那只能以短链接的方式进行传输,最终以连接结束来标识本次请求结束

当使用的是分块传输的话,如果不使用HttpObjectAggregator的话,我们需要在channelRead0处理多个HttpContent,以及最后的LastHttpContent,每个HttpContent都是变长的数据块,而通过在pipeline上的HttpServerCodec后面添加HttpObjectAggregator,可以将多个块聚合成一个完整的FullHttpRequest,方便以上层Http应用层协议的方式进行统一处理,而不用考虑底层数据传输的细节了。

参考

https://www.cnblogs.com/nxlhero/p/11670942.html
https://www.cnblogs.com/xuehaoyue/p/6639029.html

原文地址:https://www.cnblogs.com/lyhero11/p/15676944.html