Netty學習筆記之四: Netty HTTP服務的實現
阿新 • • 發佈:2019-02-07
前言
目前主流的JAVA web 的HTTP服務主要是 springMVC和Struts2,更早的有JSP/servlet。
在學習Netty的時候,發現Netty 也可以作HTTP服務,於是便將此整理一篇博文,分享給大家。
開發準備
新增配置
將Netty作為HTTP服務,需要在過濾器中新增HttpRequest之類的配置,如:
ph.addLast("encoder",new HttpResponseEncoder());
ph.addLast("decoder",new HttpRequestDecoder());
ph.addLast("aggregator" , new HttpObjectAggregator(10*1024*1024));
基本配置和之前的客戶端和服務端通訊Demo幾乎一樣,就是在業務處理器中略微的修改下業務邏輯處理就可以了。
HTTP GET請求測試
那麼進行測試。
首先啟動Netty服務,然後使用HTTP GET 方式測試(直接在瀏覽器上輸入http://localhost:6789/test)
結果如下:
HTTP服務準備
一般來說,使用HttpRequest 類作為請求,但是該類中沒有獲取訊息體的方法,獲取訊息體的類為HttpContent 或LastHttpContent,這樣獲取請求和訊息體則相當不對不方便。
在查閱Netty相關資料後,發現這樣一個請求類,可以完成上述所提的要求。這個類就是FullHttpRequest
檢視該類原始碼,可以發現該類繼承HttpRequest, FullHttpMessage,而FullHttpMessage又繼承LastHttpContent, 而LastHttpContent又繼承HttpContent,所以該類可以實現上述要求。
原始碼示例圖:
那麼程式碼修改如下:
FullHttpRequest httpRequest = (FullHttpRequest)msg;
String path=httpRequest.uri(); //獲取路徑
String body = getBody(httpRequest); //獲取引數
HttpMethod method=httpRequest.method();//獲取請求方法
然後測試POST、PUT和DELETE請求並使用json格式傳輸。
我們可以通過postman等工具來直接呼叫,就不用寫相關請求程式碼了
可以看見,Netty 作為HTTP服務可以接受基本的請求。
完整的程式碼如下:
服務端:
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;
/**
*
* Title: NettyServer
* Description: Netty服務端 Http測試
* Version:1.0.0
* @author pancm
* @date 2017年10月26日
*/
public class NettyServer {
private static final int port = 6789; //設定服務端埠
private static EventLoopGroup group = new NioEventLoopGroup(); // 通過nio方式來接收連線和處理連線
private static ServerBootstrap b = new ServerBootstrap();
/**
* Netty建立全部都是實現自AbstractBootstrap。
* 客戶端的是Bootstrap,服務端的則是 ServerBootstrap。
**/
public static void main(String[] args) throws InterruptedException {
try {
b.group(group);
b.channel(NioServerSocketChannel.class);
b.childHandler(new NettyServerFilter()); //設定過濾器
// 伺服器繫結埠監聽
ChannelFuture f = b.bind(port).sync();
System.out.println("服務端啟動成功,埠是:"+port);
// 監聽伺服器關閉監聽
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully(); //關閉EventLoopGroup,釋放掉所有資源包括建立的執行緒
}
}
}
服務端過濾器:
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
/**
*
* Title: NettyServerFilter
* Description: Netty 服務端過濾器
* Version:1.0.0
* @author pancm
* @date 2017年10月26日
*/
public class NettyServerFilter extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline ph = ch.pipeline();
//處理http服務的關鍵handler
ph.addLast("encoder",new HttpResponseEncoder());
ph.addLast("decoder",new HttpRequestDecoder());
ph.addLast("aggregator", new HttpObjectAggregator(10*1024*1024));
ph.addLast("handler", new NettyServerHandler());// 服務端業務邏輯
}
}
服務端業務邏輯
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.CharsetUtil;
import java.net.InetAddress;
/**
*
* Title: NettyServerHandler
* Description: 服務端業務邏輯
* Version:1.0.0
* @author pancm
* @date 2017年10月26日
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
private String result="";
/*
* 收到訊息時,返回資訊
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if(! (msg instanceof FullHttpRequest)){
result="未知請求!";
send(ctx,result,HttpResponseStatus.BAD_REQUEST);
return;
}
FullHttpRequest httpRequest = (FullHttpRequest)msg;
try{
String path=httpRequest.uri(); //獲取路徑
String body = getBody(httpRequest); //獲取引數
HttpMethod method=httpRequest.method();//獲取請求方法
//如果不是這個路徑,就直接返回錯誤
if(!"/test".equalsIgnoreCase(path)){
result="非法請求!";
send(ctx,result,HttpResponseStatus.BAD_REQUEST);
return;
}
System.out.println("接收到:"+method+" 請求");
//如果是GET請求
if(HttpMethod.GET.equals(method)){
//接受到的訊息,做業務邏輯處理...
System.out.println("body:"+body);
result="GET請求";
send(ctx,result,HttpResponseStatus.OK);
return;
}
//如果是POST請求
if(HttpMethod.POST.equals(method)){
//接受到的訊息,做業務邏輯處理...
System.out.println("body:"+body);
result="POST請求";
send(ctx,result,HttpResponseStatus.OK);
return;
}
//如果是PUT請求
if(HttpMethod.PUT.equals(method)){
//接受到的訊息,做業務邏輯處理...
System.out.println("body:"+body);
result="PUT請求";
send(ctx,result,HttpResponseStatus.OK);
return;
}
//如果是DELETE請求
if(HttpMethod.DELETE.equals(method)){
//接受到的訊息,做業務邏輯處理...
System.out.println("body:"+body);
result="DELETE請求";
send(ctx,result,HttpResponseStatus.OK);
return;
}
}catch(Exception e){
System.out.println("處理請求失敗!");
e.printStackTrace();
}finally{
//釋放請求
httpRequest.release();
}
}
/**
* 獲取body引數
* @param request
* @return
*/
private String getBody(FullHttpRequest request){
ByteBuf buf = request.content();
return buf.toString(CharsetUtil.UTF_8);
}
/**
* 傳送的返回值
* @param ctx 返回
* @param context 訊息
* @param status 狀態
*/
private void send(ChannelHandlerContext ctx, String context,HttpResponseStatus status) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(context, CharsetUtil.UTF_8));
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
/*
* 建立連線時,返回訊息
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("連線的客戶端地址:" + ctx.channel().remoteAddress());
ctx.writeAndFlush("客戶端"+ InetAddress.getLocalHost().getHostName() + "成功與服務端建立連線! ");
super.channelActive(ctx);
}
}