netty系列之:輕輕松松搭個支持中文的服務器


簡介

之前講了那么多關於netty的文章,都是講netty的底層原理和實現,各位小伙伴一定都在想了,看了這么多篇文章,netty到底能干啥呢?今天讓我們來使用netty簡簡單單搭一個支持中文的服務器,展示一下netty的威力。

netty的HTTP支持

今天我們搭的服務器是支持HTTP1.1的服務器。在netty中搭建服務器就像是拼房子,找到合適的工具就可以事半功倍。那么要搭建HTTP的房子,netty提供了什么樣的工具呢?

在講解netty對HTTP的支持之前,我們先看一下HTTP的版本發展情況。

HTTP的全稱是Hypertext Transfer Protocol,是在1989年World Wide Web發展起來之后出現的標准協議,用來在WWW上傳輸數據。HTTP/1.1是1997年在原始的HTTP協議基礎上進行的補充和優化。

到了2015年,為了適應快速發送的web應用和現代瀏覽器的需求,發展出了新的HTTP/2協議,主要在手機瀏覽器、延時處理、圖像處理和視頻處理方面進行了優化。

基本上所有的現代瀏覽器都支持HTTP/2協議了,但是還有很多應用程序使用的是老的HTTP/1.1協議。netty為HTTP2和HTTP1提供了不同的支持包,對於HTTP1的支持包叫做netty-codec-http,對HTTP2支持的包叫做netty-codec-http2。

本文會講解netty對HTTP1的支持,將會在后續的文章中繼續HTTP2的介紹。

netty-codec-http提供了對HTTP的非常有用的一些封裝。

首先是代表HTTP中傳輸對象的類HttpObject,這個類代表着傳輸中的所有對象。繼承這個類的對象有兩個非常重要的對象,分別是HttpMessage和HttpContent。

HttpMessage可能跟我想象的不太一樣,它實際上只包含了兩部分內容,分別是HttpVersion和HttpHeaders,但是並不包含任何內容。

public interface HttpMessage extends HttpObject {

    HttpVersion protocolVersion();

    HttpMessage setProtocolVersion(HttpVersion version);

    HttpHeaders headers();
}

這里HttpVersion只支持HTTP/1.0和HTTP/1.1協議。而HttpHeaders就是對HTTP請求中頭對象的封裝。

HttpMessage的子類是HttpRequest和HttpResponse,所以這兩個類本身是不帶請求內容的。

而具體請求的內容是在HttpContent中,HttpContent繼承自ByteBufHolder,表示它中間可以帶有ByteBuf的內容信息。

而HttpContent真正的實現類就是DefaultFullHttpRequest和DefaultFullHttpResponse,這兩個內包含了HTTP頭和HTTP請求響應內容信息。

那么問題來了,為什么要把HTTP頭和HTTP內容分開呢?

這就涉及到HTTP1.1中消息傳輸中的壓縮機制了。為了提升傳輸的效率,一般來說在傳輸的的過程中都會對象消息進行壓縮,但是對於HTTP1.1來說,頭部的內容是沒辦法壓縮的,只能壓縮content部分,所以需要區別對待。

netty中使用HTTP的原理

我們知道netty底層是客戶端和服務器端構建通道,通過通道來傳輸ByteBuf消息。那么netty是怎么支持HTTP請求呢?

當客戶端向服務器端發送HTTP請求之后,服務器端需要把接收到的數據使用解碼器解碼成為可以被應用程序使用的各種HttpObject對象,從而能夠在應用程序中對其解析。

netty提供了HttpResponseEncoder和HttpRequestDecoder類,來對HTTP的消息進行編碼和解碼。

如果不想分別使用兩個類來進行編碼和解碼,netty還提供了HttpServerCodec類來進行編碼和解碼工作。

這個類包含了HttpRequestDecoder和HttpResponseEncoder兩部分的工作,可以同時用來進行編碼和解碼。

100 (Continue) Status

在HTTP中有一個獨特的功能叫做,100 (Continue) Status,就是說client在不確定server端是否會接收請求的時候,可以先發送一個請求頭,並在這個頭上加一個"100-continue"字段,但是暫時還不發送請求body。直到接收到服務器端的響應之后再發送請求body。

為了處理這種請求,netty提供了一個HttpServerExpectContinueHandler對象,用來處理100 Status的情況。

當然,如果你的客戶端沒有這種請求,那么可以直接使用HttpObjectAggregator來將HttpMessage和HttpContent和合並成為FullHttpRequest或者FullHttpResponse。

為netty搭建HTTP服務器

有了上面的工作,我們就可以使用netty搭建http服務器了。最關鍵的一點就是在HttpRequestServerInitializer添加對應的codec和自定義handler。

    public void initChannel(SocketChannel ch) {
        ChannelPipeline p = ch.pipeline();
        p.addLast(new HttpServerCodec());
        p.addLast(new HttpServerExpectContinueHandler());
        p.addLast(new HttpRequestServerHandler());
    }

在自定義的handler中,我們需要實現一個功能,就是當收到客戶端的請求時候,需要返回給客戶端一段歡迎語。

首先將獲得的HttpObject轉換成為HttpRequest對象,然后根據請求對象構建一個DefaultFullHttpResponse對象,然后設置該response對象的頭,最后將該對象寫到channel中。

對應的關鍵代碼如下:

 private static final byte[] CONTENT = "歡迎來到www.flydean.com!".getBytes(StandardCharsets.UTF_8);

    public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) {
        if (msg instanceof HttpRequest) {
            HttpRequest req = (HttpRequest) msg;

            boolean keepAlive = HttpUtil.isKeepAlive(req);
            FullHttpResponse response = new DefaultFullHttpResponse(req.protocolVersion(), OK,
                                                                    Unpooled.wrappedBuffer(CONTENT));
            response.headers()
//                    .set(CONTENT_TYPE, TEXT_PLAIN)
                    .set(CONTENT_TYPE, "text/plain;charset=utf-8")
                    .setInt(CONTENT_LENGTH, response.content().readableBytes());

            if (keepAlive) {
                if (!req.protocolVersion().isKeepAliveDefault()) {
                    //設置header connection=keep-alive
                    response.headers().set(CONNECTION, KEEP_ALIVE);
                }
            } else {
                // 如果keepAlive是false,則設置header connection=close
                response.headers().set(CONNECTION, CLOSE);
            }
            ChannelFuture f = ctx.write(response);
            if (!keepAlive) {
                f.addListener(ChannelFutureListener.CLOSE);
            }
        }
    }

上面的關鍵代碼中CONTENT包含了中文字符串,我們使用getBytes將其轉換成了UTF-8編碼的byte數組。那么如果要想客戶端能夠正確識別UTF-8編碼,需要在response的header中設置內容類型文件為:"text/plain;charset=utf-8"。

最后,使用下面的代碼啟動server:

 // server配置
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.option(ChannelOption.SO_BACKLOG, 1024);
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new HttpRequestServerInitializer());

            Channel ch = b.bind(PORT).sync().channel();
            log.info("請打開你的瀏覽器,訪問 http://127.0.0.1:8000/");
            ch.closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

總結

現在,使用你的瀏覽器訪問你搭建的服務器地址,你就可以得到"歡迎來到www.flydean.com!"。 到此一個簡單的netty服務器就完成了。

本文的例子可以參考:learn-netty4

本文已收錄於 http://www.flydean.com/19-netty-http-client-request/

最通俗的解讀,最深刻的干貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程序那些事」,懂技術,更懂你!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM