使用Netty實現HTTP伺服器
使用Netty實現HTTP伺服器,使用Netty實現httpserver。
Netty是一個非同步事件驅動的網路應用程式框架用於快速開發可維護的高效能協議伺服器和客戶端。Netty經過精心設計,具有豐富的協議,如FTP,SMTP,HTTP以及各種二進位制和基於文字的傳統協議。
Java程式設計師在開發web應用的時候,截止2018年大多數公司採用的還是servlet規範的那一套來開發的,比如springmvc。雖然在2018年Java程式設計師們可以選擇使用spring5中的webflux,但是這個轉變沒那麼快。然而,基於servlet那一套的springmvc效能很差,如果你厭煩了,你大可以使用netty來實現一個web框架。假設你想使用netty來實現,那麼第一步你得會使用Netty啟動一個HTTP伺服器,下面開始吧。
本文HttpServer的實現目標
本文只是為了演示如何使用Netty來實現一個HTTP伺服器,如果要實現一個完整的,那將是十分複雜的。所以,我們只實現最基本的,請求-響應。具體來說是這樣的:
1. 啟動服務
2. 客戶端訪問伺服器,如:http://localhost:8081/index
3. 伺服器返回 : 你請求的uri為:/index
建立server
netty 的api設計非常好,具有通用性,幾乎就是一個固定模式的感覺。server端的啟動和客戶端的啟動程式碼十分相似。啟動server的時候指定初始化器,在初始化器中,我們可以放一個一個的handler,而具體業務邏輯處理就是放在這一個個的handler中的。寫好的server端程式碼如下:
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import java.net.InetSocketAddress; /** * netty server * 2018/11/1. */ public class HttpServer { int port ; public HttpServer(int port){ this.port = port; } public void start() throws Exception{ ServerBootstrap bootstrap = new ServerBootstrap(); EventLoopGroup boss = new NioEventLoopGroup(); EventLoopGroup work = new NioEventLoopGroup(); bootstrap.group(boss,work) .handler(new LoggingHandler(LogLevel.DEBUG)) .channel(NioServerSocketChannel.class) .childHandler(new HttpServerInitializer()); ChannelFuture f = bootstrap.bind(new InetSocketAddress(port)).sync(); System.out.println(" server start up on port : " + port); f.channel().closeFuture().sync(); } }
server端程式碼就這麼多,看起來很長,但是這就是一個樣板程式碼,你需要著重留意的就是childHandler(new HttpServerInitializer());
這一行。如果你對netty還不是十分熟悉,那麼你不需要著急把每一行的程式碼都看懂。這段程式碼翻譯成可以理解的文字是這樣的:
1.bootstrap為啟動引導器。
2.指定了使用兩個時間迴圈器。EventLoopGroup
3.指定使用Nio模式。(NioServerSocketChannel.class)
4.初始化器為HttpServerInitializer
server啟動程式碼就是這麼多,我們注意看 HttpServerInitializer
做了什麼。
在HttpServerInitializer 中新增server配置
HttpServerInitializer
其實就是一個ChannelInitializer
,在這裡我們可以指定我們的handler。前面我們說過handler是用來承載我們具體邏輯實現程式碼的地方,我們需要在ChannelInitializer
中加入我們的特殊實現。程式碼如下:
public class HttpServerInitializer extends ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new HttpServerCodec());// http 編解碼
pipeline.addLast("httpAggregator",new HttpObjectAggregator(512*1024)); // http 訊息聚合器 512*1024為接收的最大contentlength
pipeline.addLast(new HttpRequestHandler());// 請求處理器
}
}
上面程式碼很簡單,需要解釋的點如下:
1. channel 代表了一個socket.
2. ChannelPipeline 就是一個“羊肉串”,這個“羊肉串”裡邊的每一塊羊肉就是一個 handler.
handler分為兩種,inbound handler,outbound handler 。顧名思義,分別處理 流入,流出。
3. HttpServerCodec 是 http訊息的編解碼器。
4. HttpObjectAggregator是Http訊息聚合器,Aggregator這個單次就是“聚合,聚集”的意思。http訊息在傳輸的過程中可能是一片片的訊息片端,所以當伺服器接收到的是一片片的時候,就需要HttpObjectAggregator來把它們聚合起來。
5. 接收到請求之後,你要做什麼,準備怎麼做,就在HttpRequestHandler中實現。
httpserver處理請求
上面的展示了 server端啟動的程式碼,然後又展示了 server端初始化器的程式碼。下面我們來看看,請求處理的handler的程式碼:
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
//100 Continue
if (is100ContinueExpected(req)) {
ctx.write(new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.CONTINUE));
}
// 獲取請求的uri
String uri = req.uri();
Map<String,String> resMap = new HashMap<>();
resMap.put("method",req.method().name());
resMap.put("uri",uri);
String msg = "<html><head><title>test</title></head><body>你請求uri為:" + uri+"</body></html>";
// 建立http響應
FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8));
// 設定頭資訊
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
//response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
// 將html write到客戶端
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
}
上面程式碼的意思,我用註釋標明瞭,邏輯很簡單。無論是FullHttpResponse
,還是 ctx.writeAndFlush
都是netty的api,看名知意即可。半蒙半猜之下,也可以看明白這個handler其實是:1 .獲取請求uri,2. 組裝返回的響應內容,3. 響應對融到客戶端。需要解釋的是100 Continue的問題:
100 Continue含義
HTTP客戶端程式有一個實體的主體部分要傳送給伺服器,但希望在傳送之前檢視下伺服器是否會接受這個實體,所以在傳送實體之前先發送了一個攜帶100 Continue的Expect請求首部的請求。
伺服器在收到這樣的請求後,應該用 100 Continue或一條錯誤碼來進行響應。
啟動Http伺服器,演示效果
上面的程式碼寫完了,看的再多,不如執行起來跑一把。上面的3個類已經包含了我們目標中的服務端的完整程式碼。我們只需要在main函式中將其啟動即可,程式碼如下:
public class Application {
public static void main(String[] args) throws Exception{
HttpServer server = new HttpServer(8081);// 8081為啟動埠
server.start();
}
}
執行這個main函式,在瀏覽器中訪問:http://localhost:8081/index ,http://localhost:8081/text/test 試試看吧。
至此,我們基於Netty的簡易的Http伺服器實現了(如果可以稱作“HTTP伺服器”的話)。 假如我們想要實現,訪問 /index.html
就返回index.html
頁面,訪問/productList
就返回“商品列表JSON”,那麼我們還需要做請求路由,還要加入JSON序列化支援,還要根據不同的請求型別調整HTTP響應頭。本篇就不做展開了,本篇的目標是為了試下一個最簡單的Http伺服器。