Java基於Netty實現Http server的實戰
HTTP協議基礎知識
HTTP(超文本傳輸協議,英文:HyperText Transfer Protocol,縮寫:HTTP)是基於TCP/IP協議的應用層的協議,常用於分佈式、協作式和超媒體信息系統的應用層協議。
http協議的主要特點:
(1)支持CS(客戶端/服務器)模式。
(2)使用簡單,指定URL並攜帶必要的參數即可。
(3)靈活。傳輸非常多類型的數據對象,通過Content-Type指定即可。
(4)無狀態。
我們日常瀏覽器輸入一個url,請求服務器就是用的http協議,url的格式如下:
http請求體的組成:
- 方法
- uri
- http版本
- 參數
請求方法:
- GET 請求獲取Request-URI所標識的資源
- POST 在Request-URI所標識的資源後附加新的數據
- HEAD 請求獲取由Request-URI所標識的資源的響應消息報頭
- PUT 請求服務器存儲一個資源,並用Request-URI作為其標識
- DELETE 請求服務器刪除Request-URI所標識的資源
- TRACE 請求服務器回送收到的請求信息,主要用於測試或診斷
- CONNECT 保留將來使用
- OPTIONS 請求查詢服務器的性能,或者查詢與資源相關的選項和需求
響應狀態碼:
- 1xx Informational
- 2xx Success
- 3xx Redirection
- 4xx Client Error
- 5xx Server Error
HTTP 1 VS HTTP 2:
http 1不支持長連接,每次請求建立連接,響應後就關閉連接。HTTP2支持長連接,連接復用。
Netty的http協議棧
netty提供瞭對http/https協議的支持,我們可以基於netty很容易寫出http應用程序。
(1)編解碼
- HttpRequestEncoder 對 HTTP 請求進行編碼,用於客戶端出參
- HttpResponseEncoder 對 HTTP 響應進行編碼,用於服務端出參
- HttpRequestDecoder 對 HTTP 請求進行解碼,用於服務端入參處理
- HttpResponseDecoder 對 HTTP 響應進行解碼,用於客戶端對響應結果解析解析
(2)請求體FullHttpRequest和響應體FullHttpResponse
FullHttpRequest
- HttpRequest:請求頭信息對象;
- HttpContent:請求正文對象,一個 FullHttpRequest 中可以包含多個 HttpContent;
- LastHttpContent:標記請求正文的結束,可能會包含請求頭的尾部信息;
FullHttpResponse
- HttpResponse:響應頭信息對象;
- HttpContent:響應正文對象,一個 FullHttpResponse 中可以包含多個 HttpContent;
- LastHttpContent:標記響應正文的結束,可能會包含響應頭的尾部信息;
(3)HttpObjectAggregator-http消息聚合器
http的post請求包含3部分:
- request line(method、uri、protocol version)
- header
- body
HttpObjectAggregator能夠把多個部分整合在一個java對象中(另外消息體比較大的時候,還可能會分成多個消息,都會被聚合成一個整體對象),方便使用。
基於Netty實現http server
整體架構設計:
核心類SimpleHttpServer:
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; 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 lombok.extern.slf4j.Slf4j; /** * http server based netty * * @author summer * @version $Id: SimpleHttpServer.java, v 0.1 2022年01月26日 9:34 AM summer Exp $ */ @Slf4j public class SimpleHttpServer { /** * host */ private final static String host = "127.0.0.1"; /** * 端口號 */ private final static Integer port = 8085; /** * netty服務端啟動方法 */ public void start() { log.info("SimpleHttpServer start begin "); EventLoopGroup bossEventLoopGroup = new NioEventLoopGroup(1); EventLoopGroup workerEventLoopGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap() .group(bossEventLoopGroup, workerEventLoopGroup) .channel(NioServerSocketChannel.class) //開啟tcp nagle算法 .childOption(ChannelOption.TCP_NODELAY, true) //開啟長連接 .childOption(ChannelOption.SO_KEEPALIVE, true) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel c) { c.pipeline().addLast(new HttpRequestDecoder()) .addLast(new HttpResponseEncoder()) .addLast(new HttpObjectAggregator(512 * 1024)) .addLast(new SimpleHttpServerHandler()); } }); ChannelFuture channelFuture = serverBootstrap.bind(host, port).sync(); log.info("SimpleHttpServer start at port " + port); channelFuture.channel().closeFuture().sync(); } catch (Exception e) { log.error("SimpleHttpServer start exception,", e); } finally { log.info("SimpleHttpServer shutdown bossEventLoopGroup&workerEventLoopGroup gracefully"); bossEventLoopGroup.shutdownGracefully(); workerEventLoopGroup.shutdownGracefully(); } } }
實際處理請求的netty handler是SimpleHttpServerHandler:
import com.alibaba.fastjson.JSON; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.*; import lombok.extern.slf4j.Slf4j; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; /** * http服務端處理handler實現 * * @author summer * @version $Id: SimpleHttpServerHandler.java, v 0.1 2022年01月26日 9:44 AM summer Exp $ */ @Slf4j public class SimpleHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> { @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) { try { log.info("SimpleHttpServerHandler receive fullHttpRequest=" + fullHttpRequest); String result = doHandle(fullHttpRequest); log.info("SimpleHttpServerHandler,result=" + result); byte[] responseBytes = result.getBytes(StandardCharsets.UTF_8); FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_0, HttpResponseStatus.OK, Unpooled.wrappedBuffer(responseBytes)); response.headers().set("Content-Type", "text/html; charset=utf-8"); response.headers().setInt("Content-Length", response.content().readableBytes()); boolean isKeepAlive = HttpUtil.isKeepAlive(response); if (!isKeepAlive) { ctx.write(response).addListener(ChannelFutureListener.CLOSE); } else { response.headers().set("Connection", "keep-alive"); ctx.write(response); } } catch (Exception e) { log.error("channelRead0 exception,", e); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); } /** * 實際處理 * * @param fullHttpRequest HTTP請求參數 * @return */ private String doHandle(FullHttpRequest fullHttpRequest) { if (HttpMethod.GET == fullHttpRequest.method()) { QueryStringDecoder queryStringDecoder = new QueryStringDecoder(fullHttpRequest.uri()); Map<String, List<String>> params = queryStringDecoder.parameters(); return JSON.toJSONString(params); } else if (HttpMethod.POST == fullHttpRequest.method()) { return fullHttpRequest.content().toString(); } return ""; } }
在主線程中啟動服務端:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SimplehttpserverApplication { public static void main(String[] args) { SimpleHttpServer simpleHttpServer = new SimpleHttpServer(); simpleHttpServer.start(); SpringApplication.run(SimplehttpserverApplication.class, args); } }
啟動成功:
利用postman工具發起http請求進行測試,這裡簡單測試get請求:
http://127.0.0.1:8085/?name=name1&value=v2
服務端日志也打印出瞭處理情況,處理正常:
19:24:59.629 [nioEventLoopGroup-3-3] INFO com.summer.simplehttpserver.SimpleHttpServerHandler – SimpleHttpServerHandler receive fullHttpRequest=HttpObjectAggregator$AggregatedFullHttpRequest(decodeResult: success, version: HTTP/1.1, content: CompositeByteBuf(ridx: 0, widx: 0, cap: 0, components=0))
GET /?name=name1&value=v2 HTTP/1.1
User-Agent: PostmanRuntime/7.26.8
Accept: */*
Cache-Control: no-cache
Postman-Token: 7dda7e2d-6f76-4008-8b74-c2b7d78f4b2e
Host: 127.0.0.1:8085
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
content-length: 0
19:24:59.629 [nioEventLoopGroup-3-3] INFO com.summer.simplehttpserver.SimpleHttpServerHandler – SimpleHttpServerHandler,result={"name":["name1"],"value":["v2"]}
到此這篇關於Java基於Netty實現Http server的實戰的文章就介紹到這瞭,更多相關Java Netty實現Http server內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 關於Netty–Http請求處理方式
- Java遊戲服務器系列之Netty相關知識總結
- gateway、webflux、reactor-netty請求日志輸出方式
- Springboot中用 Netty 開啟UDP服務方式
- Netty 輕松實現文件上傳功能