NettyExample 1 構建簡單的http服務

NettyExample 1 構建簡單的http服務

netty 最新版本是netty-4.1.15.Final. 本來還有Netty5,但官網已經放棄5.0了,maven倉庫倒是還有5.0的版本。下載netty-4.1.15.Final源碼包,裏面包含一個 netty-example-4.1.15.Final-sources.jar文件,提供了比較豐富的example例子。

Netty Http服務端代碼實現
import com.mdq.HttpServerTest.netty.handler.HttpServerInboundHandler;
import com.mdq.HttpServerTest.netty.handler.TestInBoundHandler1;
import com.mdq.HttpServerTest.netty.handler.TestOutBoundHandler1;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
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 io.netty.handler.codec.http.cors.CorsConfig;
import io.netty.handler.codec.http.cors.CorsConfigBuilder;
import io.netty.handler.codec.http.cors.CorsHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.handler.stream.ChunkedWriteHandler;

public class NettyHttpServer {
     private static Logger log = LoggerFactory.getLogger(NettyHttpServer.class);
     static final boolean SSL = System.getProperty("ssl") != null;
     static final int PORT = Integer.parseInt(System.getProperty("port", SSL ? "8443" : "8080"));

     public void start(int port) throws Exception {
           // Configure SSL.
           final SslContext sslCtx;
           if (SSL) {
                SelfSignedCertificate ssc = new SelfSignedCertificate();
                sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
           } else {
                sslCtx = null;
           }
           EventLoopGroup bossGroup = new NioEventLoopGroup();
           EventLoopGroup workerGroup = new NioEventLoopGroup();
           try {
                ServerBootstrap b = new ServerBootstrap();
                b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                         .childHandler(new HttpServerChannelInitializer(sslCtx))
                         .option(ChannelOption.SO_BACKLOG, 128)
                         .childOption(ChannelOption.SO_KEEPALIVE, true);

                ChannelFuture f = b.bind(port).sync();
                f.channel().closeFuture().sync();
           } finally {
                workerGroup.shutdownGracefully();
                bossGroup.shutdownGracefully();
           }
     }

     public class HttpServerChannelInitializer extends ChannelInitializer<SocketChannel> {
           private final SslContext sslCtx;

           public HttpServerChannelInitializer(SslContext sslCtx) {
                this.sslCtx = sslCtx;
           }

           @Override
           public void initChannel(SocketChannel ch) {
                CorsConfig corsConfig = CorsConfigBuilder.forAnyOrigin().allowNullOrigin().allowCredentials().build();
                ChannelPipeline pipeline = ch.pipeline();
                if (sslCtx != null) {
                     pipeline.addLast(sslCtx.newHandler(ch.alloc()));
                }

                pipeline.addLast("encoder", new HttpResponseEncoder());// server端發送httpResponse,需要進行編碼
                pipeline.addLast("outHandler1", new TestOutBoundHandler1("OutHandler1"));

                pipeline.addLast("devoder", new HttpRequestDecoder()); // server端接收httpRequest,需要進行解碼
                // 聚合器,把多個消息轉換爲一個單一的FullHttpRequest或是FullHttpResponse
                pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
                pipeline.addLast(new ChunkedWriteHandler()); // 塊寫入處理器
                pipeline.addLast(new CorsHandler(corsConfig));
                pipeline.addLast("InHandler1", new TestInBoundHandler1("InHandler1"));
                pipeline.addLast("InHandler2", new TestInBoundHandler1("InHandler2"));
                pipeline.addLast(new HttpServerInboundHandler());
           }

     }

     public static void main(String[] args) throws Exception {
           NettyHttpServer server = new NettyHttpServer();
           log.info("Http Server listening on 8844 ...");
           server.start(8844);
     }
}
【1】、NioEventLoopGroup 是用來處理I/O操作的線程池,Netty對 EventLoopGroup 接口針對不同的傳輸協議提供了不同的實現。在本例子中,需要實例化兩個NioEventLoopGroup,通常第一個稱爲“boss”,用來accept客戶端連接,另一個稱爲“worker”,處理客戶端數據的讀寫操作。
【2】、ServerBootstrap 是啓動服務的輔助類,有關socket的參數可以通過ServerBootstrap進行設置。
【3】、這裏指定NioServerSocketChannel類初始化channel用來接受客戶端請求。
【4】、通常會爲新SocketChannel通過添加一些handler,來設置ChannelPipeline。ChannelInitializer 是一個特殊的handler,其中initChannel方法可以爲SocketChannel 的pipeline添加指定handler。
【5】、通過綁定端口8080,就可以對外提供服務了

服務端Http處理類Handler。Netty的FullHttpRequest、包含了讀取uri,獲取頭部信息,解析Post/Get請求參數
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;

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.HttpHeaderValues;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;

public class HttpServerInboundHandler extends ChannelInboundHandlerAdapter {
     private static Logger logger = LoggerFactory.getLogger(HttpServerInboundHandler.class);
     private Gson gson = new Gson();

     @Override
     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
           HashMap<String, String> params = new HashMap<String, String>();
           String taskId = UUID.randomUUID().toString().replaceAll("-", "");
           System.out.println(msg.getClass().toString());
           if (msg instanceof FullHttpRequest) {
                FullHttpRequest fullrequest = (FullHttpRequest) msg;
                String uri = fullrequest.uri(); // 獲取請求uri
                logger.info("【url :{} taskid;{}】", uri, taskId);

                System.out.println(fullrequest.headers().get("messageType")); // 獲取頭部信息

                ByteBuf buf = fullrequest.content();
                System.out.println(buf.toString(io.netty.util.CharsetUtil.UTF_8));// 打印請求的數據包
                buf.release();

                params = RequestParse.parseHttp(fullrequest, ""); // 解析get/post請求參數

                String res = "I am OK";
                FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                           HttpResponseStatus.OK,
                          Unpooled.wrappedBuffer(res.getBytes("UTF-8")));
                response.headers().set(CONTENT_TYPE, "text/plain");
                response.headers().set(CONTENT_TYPE, response.content().readableBytes());

                boolean keepAlive = HttpUtil.isKeepAlive(fullrequest);
                if (keepAlive) {
                     response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
                     ctx.write(response);
                } else {
                     ctx.write(response).addListener(ChannelFutureListener.CLOSE);
                }
                ctx.flush();
           }
     }
     @Override
     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
           ctx.flush();
     }
     @Override
     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
           logger.error(cause.getMessage());
           ctx.close();
     }
}
自定義的ChannelInboundHandlerAdapter
public class TestInBoundHandler1 extends ChannelInboundHandlerAdapter{
     private static Logger logger = LoggerFactory.getLogger(TestInBoundHandler1.class);
     private String message;

     public TestInBoundHandler1(String message) {
           this.message = message;
     }
     @Override
     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
           logger.info("this is TestInBoundHandler, message is {}",message);
           super.channelRead(ctx, msg);
     }
}
自定義的ChannelOutboundHandlerAdapter
public class TestOutBoundHandler1 extends ChannelOutboundHandlerAdapter{
     private static Logger logger = LoggerFactory.getLogger(TestOutBoundHandler1.class);

     private String message;

     public TestOutBoundHandler1(String message) {
           this.message = message;
     }
     @Override
     public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
           logger.info("this is TestOutBoundHandler, message : {}",message);
           super.write(ctx, msg, promise);
     }
}

Http請求參數解析類
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import io.netty.handler.codec.http.multipart.MemoryAttribute;

public class RequestParse {
     private static Logger logger = LoggerFactory.getLogger(RequestParse.class);
     private static final HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);
     private static Gson gson = new Gson();

     /**
      * 解析Netty HttpRequest的請求參數,GET/POST的解析結果都爲一個Map
      * @param request
      * @param taskId  本次請求taskId
      * @return HashMap<String, String>
      */
     public static HashMap<String, String> parseHttp(HttpRequest request, String taskId) throws Exception {
           HashMap<String, String> params = new HashMap<String, String>();
           HttpMethod method = request.method();
           String uri = request.uri();
           if (HttpMethod.GET.equals(method)) {
                QueryStringDecoder paramdecoder = new QueryStringDecoder(uri);
                for (Entry<String, List<String>> e : paramdecoder.parameters().entrySet()) {
                     params.put(e.getKey(), e.getValue().get(0));
                }
                logger.info("【GET 接受參數】:{}", gson.toJson(params));
           } else if (HttpMethod.POST.equals(method)) {
                HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(
                    new DefaultHttpDataFactory(false), request);
            List<InterfaceHttpData> postData = decoder.getBodyHttpDatas(); //
            for(InterfaceHttpData data:postData){
                if (data.getHttpDataType() == HttpDataType.Attribute) {
                    MemoryAttribute attribute = (MemoryAttribute) data;
                    params.put(attribute.getName(), attribute.getValue());
                }
            }
                logger.info("【POST 接受參數】:{}", gson.toJson(params));
           } else {
                throw new MethodNotSupportedException("not sopport such method. please use CET or POST");

           }
           return params;
     }
}


Netty Http客戶端
public class NettyHttpClient {
     public void connect(String host, int port) throws Exception {
           EventLoopGroup workerGroup = new NioEventLoopGroup();

           try {
                Bootstrap b = new Bootstrap();
                b.group(workerGroup);
                b.channel(NioSocketChannel.class);
                b.option(ChannelOption.SO_KEEPALIVE, true);
                b.handler(new ChannelInitializer<SocketChannel>() {
                     @Override
                     public void initChannel(SocketChannel ch) throws Exception {
                           // 客戶端接收到的是httpResponse響應,所以要使用HttpResponseDecoder進行解碼
                           ch.pipeline().addLast(new HttpResponseDecoder());
                           // 客戶端發送的是httprequest,所以要使用HttpRequestEncoder進行編碼
                           ch.pipeline().addLast(new HttpRequestEncoder());
                           ch.pipeline().addLast(new HttpClientInboundHandler());
                     }
                });
                // Start the client.
                ChannelFuture f = b.connect(host, port).sync(); // (5)

                String uriSting = new URI("http://127.0.0.1:8844?name=mm&age=12").toASCIIString();
                String msg = "Are you ok?";
                DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uriSting,
                          Unpooled.wrappedBuffer(msg.getBytes()));

                // 構建http請求
                request.headers().set(HttpHeaderNames.HOST, host);
                request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
                request.headers().set(HttpHeaderNames.CONTENT_LENGTH, request.content().readableBytes());
                request.headers().set("messageType", "normal");
                request.headers().set("businessType", "testServerState");
                // 發送http請求
                f.channel().write(request);
                f.channel().flush();
                f.channel().closeFuture().sync();
           } finally {
                workerGroup.shutdownGracefully();
           }

     }

     public static void main(String[] args) throws Exception {
           NettyHttpClient client = new NettyHttpClient();
           client.connect("127.0.0.1", 8844);
     }
}

Netty 客戶端Handler處理
public class HttpClientInboundHandler extends ChannelInboundHandlerAdapter {
     @Override
     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
           if (msg instanceof HttpResponse) {
                HttpResponse response = (HttpResponse) msg;
                System.out.println("CONTENT_TYPE:" + response.headers().get(HttpHeaderNames.CONTENT_TYPE));
           }
           if (msg instanceof HttpContent) {
                HttpContent content = (HttpContent) msg;
                ByteBuf buf = content.content();
                System.out.println(buf.toString(io.netty.util.CharsetUtil.UTF_8));
                buf.release();
           }
     }
}

HttpMethod:主要是對method的封裝,可以獲取請求方式
HttpVersion: 對version的封裝,netty包含1.0和1.1的版本
HttpHeaders:包含對header的內容進行封裝及操作,可以通過headers()獲取頭信息
HttpContent:是對body進行封裝,本質上就是一個ByteBuf。如果ByteBuf的長度是固定的,則請求的body過大,可能包含多個HttpContent,其中最後一個爲LastHttpContent(空的HttpContent),用來說明body的結束。
HttpRequest:主要包含對Request Line和Header的組合
FullHttpRequest: 主要包含對HttpRequest和httpContent的組合。繼承了HttpRequest和httpContent

QueryStringDecoder: 主要是對url進行封裝,解析path和url上面的參數。(Tips:在tomcat中如果提交的post請求是application/x-www-form-urlencoded,則getParameter獲取的是包含url後面和body裏面所有的參數,而在netty中,獲取的僅僅是url上面的參數)
HttpPostRequestDecoder:通過request請求創建一個HttpPostRequestDecoder。對HttpContent進行解析,獲取Post請求的數據報並且解析參數。注意的是要求request是FullHttpRequest,因爲單純的HttpRequest不包含HttpContent數據。

注意事項:
1、可以通過在Netty的Chanel中發送HttpRequest對象,完成發送http請求的要求,同時可以對HttpHeader進行設置。
2、上面涉及到的http對象都是Netty自己封裝的,不是標準的。無法使用一般的http請求,需要使用Netty創建對應的HttpClient。
(Netty服務端可以接受一般Http請求,但是請求方無法解析Netty服務端返回的HttpResponse.用Fiddler抓包工具顯示是502錯誤)。
需要加上
ChannelFuture 少了對 CLOSE 事件的監聽器
HTTP1.1 版本默認是長連接方式(也就是 Connection=Keep-Alive),而我在 netty HTTP 服務器中並沒有對長、短連接方式做區別處理,並且在 HttpResponse 響應中並沒有顯式加上 Content-Length 頭部信息,恰巧 netty Http 協議棧並沒有在框架上做這件工作,導致服務端雖然把響應消息發出去了,但是客戶端並不知道你是否發送完成了(即沒辦法判斷數據是否已經發送完)。
if (keepAlive) {
     response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
     response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
     ctx.write(response);
     // If keep-alive is off, close the connection once the content is fully written.
     ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
} else {
     ctx.write(response).addListener(ChannelFutureListener.CLOSE);
}


netty處理http請求,爲什麼HttpRequest和HttpContent是分兩次過來的?
因爲HttpRequestDecoder對Netty請求進行了解碼,會產生HttpRequest和HttpContent。可通過HttpObjectAggregator進行聚合成一個FullHttpRequest。
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));// 聚合器,把多個消息轉換爲一個單一的FullHttpRequest或是FullHttpResponse

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章