關鍵字:使用Netty實現HTTP服務器,使用Netty實現httpserver,Netty Http server
Netty是一個異步事件驅動的網絡應用程序框架用於快速開發可維護的高性能協議服務器和客戶端。Netty經過精心設計,具有豐富的協議,如FTP,SMTP,HTTP以及各種二進制和基於文本的傳統協議。
Java程序員在開發web應用的時候,截止2018年大多數公司采用的還是servlet規范的那一套來開發的,比如springmvc。雖然在2018年Java程序員們可以選擇使用spring5中的webflux,但是這個轉變沒那么快。然而,基於servlet那一套的springmvc性能很差,如果你厭煩了,你大可以使用netty來實現一個web框架。假設你想使用netty來實現,那么第一步你得會使用Netty啟動一個HTTP服務器,下面開始吧。
Netty系列的文章在這里https://www.cnblogs.com/demingblog/p/9912099.html
本文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服務器。源碼下載
使用Netty實現HTTP服務器
Netty實現心跳機制
Netty開發redis客戶端,Netty發送redis命令,netty解析redis消息
Netty系列
spring如何啟動的?這里結合spring源碼描述了啟動過程
SpringMVC是怎么工作的,SpringMVC的工作原理
spring 異常處理。結合spring源碼分析400異常處理流程及解決方法
Mybatis Mapper接口是如何找到實現類的-源碼分析