Netty是一個(gè)高性能的網(wǎng)絡(luò)編程框架,有著簡(jiǎn)單易于使用的抽象模型。利用Netty自帶的Http協(xié)議編解碼器扳抽,我們可以快速地以較少的代碼編寫一個(gè)簡(jiǎn)單的Http服務(wù)器艺糜。
本文主要介紹如何使用Netty實(shí)現(xiàn)一個(gè)極簡(jiǎn)的Http服務(wù)器异袄,主要功能是通過(guò)接收一個(gè)瀏覽器Http請(qǐng)求,Netty服務(wù)器返回一個(gè)靜態(tài)資源頁(yè)面。
為了實(shí)現(xiàn)Http服務(wù)器,我們只需要用到Netty自帶的兩個(gè)編解碼器:
- HttpServerCodec:用于Netty服務(wù)端旭寿,該類其實(shí)是
HttpRequestDecoder
和HttpResponseEncoder
的封裝,因此我們?cè)?code>ChannelPipeline中加入HttpServerCodec
即可實(shí)現(xiàn)Http請(qǐng)求的解碼和Http響應(yīng)的編碼崇败; - HttpObjectAggregator:Http請(qǐng)求經(jīng)過(guò)
HttpServerCodec
解碼之后是HttpRequest
和HttpContents
對(duì)象盅称,HttpObjectAggregator
會(huì)將多個(gè)HttpRequest
和HttpContents
對(duì)象再拼裝成一個(gè)FullHttpRequest
,再將其傳遞到下個(gè)Handler
。
首先缩膝,我們首先創(chuàng)建一個(gè)maven項(xiàng)目搭幻,在pom.xml
文件中添加netty-all
依賴即可:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.32.Final</version>
</dependency>
第二步,編寫HttpServer
類逞盆,作為Http服務(wù)啟動(dòng)的入口。此處我們通過(guò)命令行傳入Http服務(wù)啟動(dòng)的端口號(hào)松申。HttpServer
類代碼如下:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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 java.net.InetSocketAddress;
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 {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(group)
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast("codec", new HttpServerCodec())
.addLast("aggregator", new HttpObjectAggregator(512 * 1024))
.addLast(new HttpRequestHandler());
}
});
ChannelFuture f = b.bind().sync();
System.out.println(HttpServer.class.getName() + " started and listen on " + f.channel().localAddress());
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
}
接下來(lái)需要編寫我們自己的HttpRequestHandler
云芦,來(lái)處理Http請(qǐng)求相關(guān)的業(yè)務(wù)需求,即通過(guò)接收一個(gè)Http請(qǐng)求贸桶,返回一個(gè)靜態(tài)資源頁(yè)面的功能舅逸。
我們先創(chuàng)建一個(gè)index.html,并把它放到/src/main/resources
目錄下皇筛,index文件如下所示:
<html>
<head>
<title>Hello Netty</title>
<meta charset="UTF-8">
</head>
<body>
<h1>Hello Netty</h1>
<p>Netty Http Server測(cè)試</p>
</body>
</html>
HttpRequestHandler
類如下:
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import java.io.File;
import java.io.RandomAccessFile;
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
String url = this.getClass().getResource("/").getPath() + "index.html";
File file = new File(url);
RandomAccessFile raf = new RandomAccessFile(file, "r");
HttpResponse response = new DefaultHttpResponse(msg.protocolVersion(), HttpResponseStatus.OK);
ctx.write(response);
ctx.write(new DefaultFileRegion(raf.getChannel(),0, raf.length()));
ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
future.addListener(ChannelFutureListener.CLOSE);
}
}
代碼只有很少的幾行琉历。首先我們獲取index.html
所在的路徑, 創(chuàng)建RandomAccessFile
對(duì)象指向index.html
文件水醋。通過(guò)調(diào)用ChannelHandlerContext
的write
方法寫入一個(gè)HttpResponse
旗笔,其中包含了Http協(xié)議版本和服務(wù)端200的響應(yīng)碼。接著再寫入index.html
文件內(nèi)容拄踪,最后再寫入LastHttpContent
并同時(shí)調(diào)用flush
蝇恶。最后在ChannelFuture
上增加監(jiān)聽器以便在Http響應(yīng)完成后關(guān)閉channel
。
我們通過(guò)命令行啟動(dòng)Http服務(wù)器惶桐,同時(shí)指定相應(yīng)端口撮弧。打開瀏覽器訪問(wèn)后效果如下: