NettyExample 1 構建簡單的http服務
netty 最新版本是netty-4.1.15.Final. 本來還有Netty5,但官網已經放棄5.0了,maven倉庫倒是還有5.0的版本。下載netty-4.1.15.Final源碼包,裏面包含一個 netty-example-4.1.15.Final-sources.jar文件,提供了比較豐富的example例子。
Netty Http服務端代碼實現
import com.mdq.HttpServerTest.netty.handler.HttpServerInboundHandler;
import com.mdq.HttpServerTest.netty.handler.TestInBoundHandler1;
import com.mdq.HttpServerTest.netty.handler.TestOutBoundHandler1;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
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.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.cors.CorsConfig;
import io.netty.handler.codec.http.cors.CorsConfigBuilder;
import io.netty.handler.codec.http.cors.CorsHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.handler.stream.ChunkedWriteHandler;
public class NettyHttpServer {
private static Logger log = LoggerFactory.getLogger(NettyHttpServer.class);
static final boolean SSL = System.getProperty("ssl") != null;
static final int PORT = Integer.parseInt(System.getProperty("port", SSL ? "8443" : "8080"));
public void start(int port) throws Exception {
// Configure SSL.
final SslContext sslCtx;
if (SSL) {
SelfSignedCertificate ssc = new SelfSignedCertificate();
sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
} else {
sslCtx = null;
}
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.childHandler(new HttpServerChannelInitializer(sslCtx))
.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 class HttpServerChannelInitializer extends ChannelInitializer<SocketChannel> {
private final SslContext sslCtx;
public HttpServerChannelInitializer(SslContext sslCtx) {
this.sslCtx = sslCtx;
}
@Override
public void initChannel(SocketChannel ch) {
CorsConfig corsConfig = CorsConfigBuilder.forAnyOrigin().allowNullOrigin().allowCredentials().build();
ChannelPipeline pipeline = ch.pipeline();
if (sslCtx != null) {
pipeline.addLast(sslCtx.newHandler(ch.alloc()));
}
pipeline.addLast("encoder", new HttpResponseEncoder());// server端發送httpResponse,需要進行編碼
pipeline.addLast("outHandler1", new TestOutBoundHandler1("OutHandler1"));
pipeline.addLast("devoder", new HttpRequestDecoder()); // server端接收httpRequest,需要進行解碼
// 聚合器,把多個消息轉換爲一個單一的FullHttpRequest或是FullHttpResponse
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
pipeline.addLast(new ChunkedWriteHandler()); // 塊寫入處理器
pipeline.addLast(new CorsHandler(corsConfig));
pipeline.addLast("InHandler1", new TestInBoundHandler1("InHandler1"));
pipeline.addLast("InHandler2", new TestInBoundHandler1("InHandler2"));
pipeline.addLast(new HttpServerInboundHandler());
}
}
public static void main(String[] args) throws Exception {
NettyHttpServer server = new NettyHttpServer();
log.info("Http Server listening on 8844 ...");
server.start(8844);
}
}
【1】、NioEventLoopGroup 是用來處理I/O操作的線程池,Netty對 EventLoopGroup 接口針對不同的傳輸協議提供了不同的實現。在本例子中,需要實例化兩個NioEventLoopGroup,通常第一個稱爲“boss”,用來accept客戶端連接,另一個稱爲“worker”,處理客戶端數據的讀寫操作。
【2】、ServerBootstrap 是啓動服務的輔助類,有關socket的參數可以通過ServerBootstrap進行設置。
【3】、這裏指定NioServerSocketChannel類初始化channel用來接受客戶端請求。
【4】、通常會爲新SocketChannel通過添加一些handler,來設置ChannelPipeline。ChannelInitializer 是一個特殊的handler,其中initChannel方法可以爲SocketChannel 的pipeline添加指定handler。
【5】、通過綁定端口8080,就可以對外提供服務了
服務端Http處理類Handler。Netty的FullHttpRequest、包含了讀取uri,獲取頭部信息,解析Post/Get請求參數
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
public class HttpServerInboundHandler extends ChannelInboundHandlerAdapter {
private static Logger logger = LoggerFactory.getLogger(HttpServerInboundHandler.class);
private Gson gson = new Gson();
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
HashMap<String, String> params = new HashMap<String, String>();
String taskId = UUID.randomUUID().toString().replaceAll("-", "");
System.out.println(msg.getClass().toString());
if (msg instanceof FullHttpRequest) {
FullHttpRequest fullrequest = (FullHttpRequest) msg;
String uri = fullrequest.uri(); // 獲取請求uri
logger.info("【url :{} taskid;{}】", uri, taskId);
System.out.println(fullrequest.headers().get("messageType")); // 獲取頭部信息
ByteBuf buf = fullrequest.content();
System.out.println(buf.toString(io.netty.util.CharsetUtil.UTF_8));// 打印請求的數據包
buf.release();
params = RequestParse.parseHttp(fullrequest, ""); // 解析get/post請求參數
String res = "I am OK";
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
Unpooled.wrappedBuffer(res.getBytes("UTF-8")));
response.headers().set(CONTENT_TYPE, "text/plain");
response.headers().set(CONTENT_TYPE, response.content().readableBytes());
boolean keepAlive = HttpUtil.isKeepAlive(fullrequest);
if (keepAlive) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
ctx.write(response);
} else {
ctx.write(response).addListener(ChannelFutureListener.CLOSE);
}
ctx.flush();
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.error(cause.getMessage());
ctx.close();
}
}
自定義的ChannelInboundHandlerAdapter
public class TestInBoundHandler1 extends ChannelInboundHandlerAdapter{
private static Logger logger = LoggerFactory.getLogger(TestInBoundHandler1.class);
private String message;
public TestInBoundHandler1(String message) {
this.message = message;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
logger.info("this is TestInBoundHandler, message is {}",message);
super.channelRead(ctx, msg);
}
}
自定義的ChannelOutboundHandlerAdapter
public class TestOutBoundHandler1 extends ChannelOutboundHandlerAdapter{
private static Logger logger = LoggerFactory.getLogger(TestOutBoundHandler1.class);
private String message;
public TestOutBoundHandler1(String message) {
this.message = message;
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
logger.info("this is TestOutBoundHandler, message : {}",message);
super.write(ctx, msg, promise);
}
}
Http請求參數解析類
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import io.netty.handler.codec.http.multipart.MemoryAttribute;
public class RequestParse {
private static Logger logger = LoggerFactory.getLogger(RequestParse.class);
private static final HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);
private static Gson gson = new Gson();
/**
* 解析Netty HttpRequest的請求參數,GET/POST的解析結果都爲一個Map
* @param request
* @param taskId 本次請求taskId
* @return HashMap<String, String>
*/
public static HashMap<String, String> parseHttp(HttpRequest request, String taskId) throws Exception {
HashMap<String, String> params = new HashMap<String, String>();
HttpMethod method = request.method();
String uri = request.uri();
if (HttpMethod.GET.equals(method)) {
QueryStringDecoder paramdecoder = new QueryStringDecoder(uri);
for (Entry<String, List<String>> e : paramdecoder.parameters().entrySet()) {
params.put(e.getKey(), e.getValue().get(0));
}
logger.info("【GET 接受參數】:{}", gson.toJson(params));
} else if (HttpMethod.POST.equals(method)) {
HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(
new DefaultHttpDataFactory(false), request);
List<InterfaceHttpData> postData = decoder.getBodyHttpDatas(); //
for(InterfaceHttpData data:postData){
if (data.getHttpDataType() == HttpDataType.Attribute) {
MemoryAttribute attribute = (MemoryAttribute) data;
params.put(attribute.getName(), attribute.getValue());
}
}
logger.info("【POST 接受參數】:{}", gson.toJson(params));
} else {
throw new MethodNotSupportedException("not sopport such method. please use CET or POST");
}
return params;
}
}
Netty Http客戶端
public class NettyHttpClient {
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(); // (5)
String uriSting = new URI("http://127.0.0.1:8844?name=mm&age=12").toASCIIString();
String msg = "Are you ok?";
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uriSting,
Unpooled.wrappedBuffer(msg.getBytes()));
// 構建http請求
request.headers().set(HttpHeaderNames.HOST, host);
request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
request.headers().set(HttpHeaderNames.CONTENT_LENGTH, request.content().readableBytes());
request.headers().set("messageType", "normal");
request.headers().set("businessType", "testServerState");
// 發送http請求
f.channel().write(request);
f.channel().flush();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
NettyHttpClient client = new NettyHttpClient();
client.connect("127.0.0.1", 8844);
}
}
Netty 客戶端Handler處理
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(HttpHeaderNames.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();
}
}
}
HttpMethod:主要是對method的封裝,可以獲取請求方式
HttpVersion: 對version的封裝,netty包含1.0和1.1的版本
HttpHeaders:包含對header的內容進行封裝及操作,可以通過headers()獲取頭信息
HttpContent:是對body進行封裝,本質上就是一個ByteBuf。如果ByteBuf的長度是固定的,則請求的body過大,可能包含多個HttpContent,其中最後一個爲LastHttpContent(空的HttpContent),用來說明body的結束。
HttpRequest:主要包含對Request Line和Header的組合
FullHttpRequest: 主要包含對HttpRequest和httpContent的組合。繼承了HttpRequest和httpContent
QueryStringDecoder: 主要是對url進行封裝,解析path和url上面的參數。(Tips:在tomcat中如果提交的post請求是application/x-www-form-urlencoded,則getParameter獲取的是包含url後面和body裏面所有的參數,而在netty中,獲取的僅僅是url上面的參數)
HttpPostRequestDecoder:通過request請求創建一個HttpPostRequestDecoder。對HttpContent進行解析,獲取Post請求的數據報並且解析參數。注意的是要求request是FullHttpRequest,因爲單純的HttpRequest不包含HttpContent數據。
注意事項:
1、可以通過在Netty的Chanel中發送HttpRequest對象,完成發送http請求的要求,同時可以對HttpHeader進行設置。
2、上面涉及到的http對象都是Netty自己封裝的,不是標準的。無法使用一般的http請求,需要使用Netty創建對應的HttpClient。
(Netty服務端可以接受一般Http請求,但是請求方無法解析Netty服務端返回的HttpResponse.用Fiddler抓包工具顯示是502錯誤)。
需要加上
ChannelFuture 少了對 CLOSE 事件的監聽器
HTTP1.1 版本默認是長連接方式(也就是 Connection=Keep-Alive),而我在 netty HTTP 服務器中並沒有對長、短連接方式做區別處理,並且在 HttpResponse 響應中並沒有顯式加上 Content-Length 頭部信息,恰巧 netty Http 協議棧並沒有在框架上做這件工作,導致服務端雖然把響應消息發出去了,但是客戶端並不知道你是否發送完成了(即沒辦法判斷數據是否已經發送完)。
if (keepAlive) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
ctx.write(response);
// If keep-alive is off, close the connection once the content is fully written.
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
} else {
ctx.write(response).addListener(ChannelFutureListener.CLOSE);
}
netty處理http請求,爲什麼HttpRequest和HttpContent是分兩次過來的?
因爲HttpRequestDecoder對Netty請求進行了解碼,會產生HttpRequest和HttpContent。可通過HttpObjectAggregator進行聚合成一個FullHttpRequest。
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));// 聚合器,把多個消息轉換爲一個單一的FullHttpRequest或是FullHttpResponse