1. 程式人生 > >Netty學習筆記之四: Netty HTTP服務的實現

Netty學習筆記之四: Netty HTTP服務的實現

前言

目前主流的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

。 
檢視該類原始碼,可以發現該類繼承HttpRequestFullHttpMessage,而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);
    }
}