筆者所有文章第一時間發(fā)布于:
hhbbz的個人博客
簡介
在上一章中我們認識了netty央勒,他有三大優(yōu)點:并發(fā)高不见,傳輸快,封裝好崔步。在這一章我們來用Netty搭建一個HttpServer稳吮,從實際開發(fā)中了解netty框架的一些特性和概念。
認識Http請求
在動手寫Netty框架之前井濒,我們先要了解http請求的組成盖高,如下圖:
- HTTP Request 第一部分是包含的頭信息
- HttpContent 里面包含的是數據,可以后續(xù)有多個 HttpContent 部分
- LastHttpContent 標記是 HTTP request 的結束眼虱,同時可能包含頭的尾部信息
- 完整的 HTTP request喻奥,由1,2捏悬,3組成
- HTTP response 第一部分是包含的頭信息
- HttpContent 里面包含的是數據撞蚕,可以后續(xù)有多個 HttpContent 部分
- LastHttpContent 標記是 HTTP response 的結束,同時可能包含頭的尾部信息
- 完整的 HTTP response过牙,由1甥厦,2纺铭,3組成
從request的介紹我們可以看出來,一次http請求并不是通過一次對話完成的刀疙,他中間可能有很次的連接舶赔。通過上一章我們隊netty的了解,每一次對話都會建立一個channel谦秧,并且一個ChannelInboundHandler一般是不會同時去處理多個Channel的竟纳。
如何在一個Channel里面處理一次完整的Http請求?這就要用到我們上圖提到的FullHttpRequest疚鲤,我們只需要在使用netty處理channel的時候锥累,只處理消息是FullHttpRequest的Channel,這樣我們就能在一個ChannelHandler中處理一個完整的Http請求了集歇。
編寫代碼
搭建一個Netty服務器桶略,我們只需要兩個類——一個是啟動類,負責啟動(BootStrap)和main方法诲宇,一個是ChannelHandler际歼,負責具體的業(yè)務邏輯,我們先從啟動類說起姑蓝。
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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;
/**
* Created by RoyDeng on 17/7/20.
*/
public class HttpServer {
private final int port;
public HttpServer(int port) {
this.port = port;
}
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println(
"Usage: " + HttpServer.class.getSimpleName() +
" <port>");
return;
}
int port = Integer.parseInt(args[0]);
new HttpServer(port).start();
}
public void start() throws Exception {
ServerBootstrap b = new ServerBootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
b.group(group)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
System.out.println("initChannel ch:" + ch);
ch.pipeline()
.addLast("decoder", new HttpRequestDecoder()) // 1
.addLast("encoder", new HttpResponseEncoder()) // 2
.addLast("aggregator", new HttpObjectAggregator(512 * 1024)) // 3
.addLast("handler", new HttpHandler()); // 4
}
})
.option(ChannelOption.SO_BACKLOG, 128) // determining the number of connections queued
.childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);
b.bind(port).sync();
}
}
這個類同上一章中出現(xiàn)的Netty簡易封裝服務器代碼類似鹅心,不一樣的是這里使用了多個ChannelHandler,在這里一一介紹:
- HttpRequestDecoder它掂,用于解碼request
- HttpResponseEncoder巴帮,用于編碼response
-
aggregator,消息聚合器(重要)虐秋。為什么能有FullHttpRequest這個東西榕茧,就是因為有他,HttpObjectAggregator客给,如果沒有他用押,就不會有那個消息是FullHttpRequest的那段Channel,同樣也不會有FullHttpResponse靶剑。
如果我們將z'h - HttpObjectAggregator(512 * 1024)的參數含義是消息合并的數據大小蜻拨,如此代表聚合的消息內容長度不超過512kb鸽照。
添加我們自己的處理接口
完成啟動類之后溅话,接下來就是我們的業(yè)務處理類HttpHandler了,先上代碼:
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.AsciiString;
/**
* Created by RoyDeng on 17/7/20.
*/
public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { // 1
private AsciiString contentType = HttpHeaderValues.TEXT_PLAIN;
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
System.out.println("class:" + msg.getClass().getName());
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
Unpooled.wrappedBuffer("test".getBytes())); // 2
HttpHeaders heads = response.headers();
heads.add(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=UTF-8");
heads.add(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); // 3
heads.add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
ctx.write(response);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelReadComplete");
super.channelReadComplete(ctx);
ctx.flush(); // 4
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("exceptionCaught");
if(null != cause) cause.printStackTrace();
if(null != ctx) ctx.close();
}
}
該段代碼需要注意的地方如注釋所示蜗顽,有以下四點:
- Handler需要聲明泛型為<FullHttpRequest>坑匠,聲明之后血崭,只有msg為FullHttpRequest的消息才能進來。
由于泛型的過濾比較簡單,我們就不改代碼來驗證了夹纫,但是在這里我們可以利用泛型的特性另外做個小測試咽瓷,將泛型去掉,并且將HttpServer中
.addLast("aggregator", new HttpObjectAggregator(512 * 1024)) // 3
這一行代碼注釋掉舰讹,然后觀察注釋前后的log茅姜。
注釋前:
initChannel ch:[id: 0xcb9d8e9e, L:/0:0:0:0:0:0:0:1:8888 - R:/0:0:0:0:0:0:0:1:58855]
class:io.netty.handler.codec.http.HttpObjectAggregator$AggregatedFullHttpRequest
channelReadComplete
注釋后:
initChannel ch:[id: 0xc5415409, L:/0:0:0:0:0:0:0:1:8888 - R:/0:0:0:0:0:0:0:1:58567]
class:io.netty.handler.codec.http.DefaultHttpRequest
class:io.netty.handler.codec.http.LastHttpContent$1
channelReadComplete
channelReadComplete
從中可以看出,如果沒有aggregator月匣,那么一個http請求就會通過多個Channel被處理钻洒,這對我們的業(yè)務開發(fā)是不方便的,而aggregator的作用就在于此桶错。
- 生成response航唆,這里使用的FullHttpResponse胀蛮,同F(xiàn)ullHttpRequest類似院刁,通過這個我們就不用將response拆分成多個channel返回給請求端了。
- 添加header描述length粪狼。這一步是很重要的一步退腥,如果沒有這一步,你會發(fā)現(xiàn)用postman發(fā)出請求之后就一直在刷新再榄,因為http請求方不知道返回的數據到底有多長狡刘。
- channel讀取完成之后需要輸出緩沖流。如果沒有這一步困鸥,你會發(fā)現(xiàn)postman同樣會一直在刷新