Netty入門教程2——動手搭建HttpServer

在上一章中我們認識了netty,他有三大優(yōu)點:并發(fā)高,傳輸快,封裝好。在這一章我們來用Netty搭建一個HttpServer,從實際開發(fā)中了解netty框架的一些特性和概念。

netty.png

認識Http請求

在動手寫Netty框架之前,我們先要了解http請求的組成,如下圖:


HTTP request component parts
  1. HTTP Request 第一部分是包含的頭信息
  2. HttpContent 里面包含的是數(shù)據(jù),可以后續(xù)有多個 HttpContent 部分
  3. LastHttpContent 標記是 HTTP request 的結(jié)束,同時可能包含頭的尾部信息
  4. 完整的 HTTP request,由1,2,3組成
HTTP response component parts
  1. HTTP response 第一部分是包含的頭信息
  2. HttpContent 里面包含的是數(shù)據(jù),可以后續(xù)有多個 HttpContent 部分
  3. LastHttpContent 標記是 HTTP response 的結(jié)束,同時可能包含頭的尾部信息
  4. 完整的 HTTP response,由1,2,3組成

從request的介紹我們可以看出來,一次http請求并不是通過一次對話完成的,他中間可能有很次的連接。通過上一章我們隊netty的了解,每一次對話都會建立一個channel,并且一個ChannelInboundHandler一般是不會同時去處理多個Channel的。
如何在一個Channel里面處理一次完整的Http請求?這就要用到我們上圖提到的FullHttpRequest,我們只需要在使用netty處理channel的時候,只處理消息是FullHttpRequest的Channel,這樣我們就能在一個ChannelHandler中處理一個完整的Http請求了。

開始動手

搭建一個Netty服務(wù)器,我們只需要兩個類——一個是啟動類,負責啟動(BootStrap)和main方法,一個是ChannelHandler,負責具體的業(yè)務(wù)邏輯,我們先從啟動類說起。

package com.dz.netty.http;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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;

/**
 * Created by RoyDeng on 17/7/20.
 */
public class HttpServer {

    private final int port;

    public HttpServer(int port) {
        this.port = port;
    }

    public static void main(String[] args) throws Exception {
        if (args.length != 1) {
            System.err.println(
                    "Usage: " + HttpServer.class.getSimpleName() +
                            " <port>");
            return;
        }
        int port = Integer.parseInt(args[0]);
        new HttpServer(port).start();
    }

    public void start() throws Exception {
        ServerBootstrap b = new ServerBootstrap();
        NioEventLoopGroup group = new NioEventLoopGroup();
        b.group(group)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch)
                            throws Exception {
                        System.out.println("initChannel ch:" + ch);
                        ch.pipeline()
                                .addLast("decoder", new HttpRequestDecoder())   // 1
                                .addLast("encoder", new HttpResponseEncoder())  // 2
                                .addLast("aggregator", new HttpObjectAggregator(512 * 1024))    // 3
                                .addLast("handler", new HttpHandler());        // 4
                    }
                })
                .option(ChannelOption.SO_BACKLOG, 128) // determining the number of connections queued
                .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);

        b.bind(port).sync();
    }
}

這個類同上一章中出現(xiàn)的Netty簡易封裝服務(wù)器代碼類似,不一樣的是這里使用了多個ChannelHandler,在這里一一介紹:

  1. HttpRequestDecoder,用于解碼request
  2. HttpResponseEncoder,用于編碼response
  3. aggregator,消息聚合器(重要)。為什么能有FullHttpRequest這個東西,就是因為有他,HttpObjectAggregator,如果沒有他,就不會有那個消息是FullHttpRequest的那段Channel,同樣也不會有FullHttpResponse。
    如果我們將z'h
    HttpObjectAggregator(512 * 1024)的參數(shù)含義是消息合并的數(shù)據(jù)大小,如此代表聚合的消息內(nèi)容長度不超過512kb。
  4. 添加我們自己的處理接口

完成啟動類之后,接下來就是我們的業(yè)務(wù)處理類HttpHandler了,先上代碼:

package com.dz.netty.http;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.AsciiString;

/**
 * Created by RoyDeng on 17/7/20.
 */
public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { // 1

    private AsciiString contentType = HttpHeaderValues.TEXT_PLAIN;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
        System.out.println("class:" + msg.getClass().getName());
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                HttpResponseStatus.OK,
                Unpooled.wrappedBuffer("test".getBytes())); // 2

        HttpHeaders heads = response.headers();
        heads.add(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=UTF-8");
        heads.add(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); // 3
        heads.add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);

        ctx.write(response);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelReadComplete");
        super.channelReadComplete(ctx);
        ctx.flush(); // 4
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("exceptionCaught");
        if(null != cause) cause.printStackTrace();
        if(null != ctx) ctx.close();
    }
}

該段代碼需要注意的地方如注釋所示,有以下四點:

  1. Handler需要聲明泛型為<FullHttpRequest>,聲明之后,只有msg為FullHttpRequest的消息才能進來。
    由于泛型的過濾比較簡單,我們就不改代碼來驗證了,但是在這里我們可以利用泛型的特性另外做個小測試,將泛型去掉,并且將HttpServer中.addLast("aggregator", new HttpObjectAggregator(512 * 1024)) // 3這一行代碼注釋掉,然后觀察注釋前后的log。
    注釋前:
initChannel ch:[id: 0xcb9d8e9e, L:/0:0:0:0:0:0:0:1:8888 - R:/0:0:0:0:0:0:0:1:58855]
class:io.netty.handler.codec.http.HttpObjectAggregator$AggregatedFullHttpRequest
channelReadComplete

注釋后:

initChannel ch:[id: 0xc5415409, L:/0:0:0:0:0:0:0:1:8888 - R:/0:0:0:0:0:0:0:1:58567]
class:io.netty.handler.codec.http.DefaultHttpRequest
class:io.netty.handler.codec.http.LastHttpContent$1
channelReadComplete
channelReadComplete

從中可以看出,如果沒有aggregator,那么一個http請求就會通過多個Channel被處理,這對我們的業(yè)務(wù)開發(fā)是不方便的,而aggregator的作用就在于此。

  1. 生成response,這里使用的FullHttpResponse,同F(xiàn)ullHttpRequest類似,通過這個我們就不用將response拆分成多個channel返回給請求端了。
  2. 添加header描述length。這一步是很重要的一步,如果沒有這一步,你會發(fā)現(xiàn)用postman發(fā)出請求之后就一直在刷新,因為http請求方不知道返回的數(shù)據(jù)到底有多長。
  3. channel讀取完成之后需要輸出緩沖流。如果沒有這一步,你會發(fā)現(xiàn)postman同樣會一直在刷新。

構(gòu)建HTTPS服務(wù)

? 首先,構(gòu)建HTTPS服務(wù)需要證書,那么什么是SSL證書呢?

? SSL 證書就是遵守 SSL協(xié)議,由受信任的數(shù)字證書頒發(fā)機構(gòu)CA,在驗證服務(wù)器身份后頒發(fā),具有服務(wù)器身份驗證和數(shù)據(jù)傳輸加密功能。

? 也就是說,HTTPS相比于HTTP服務(wù),能夠防止網(wǎng)絡(luò)劫持,同時具備一定的安全加密作用。

? 一般來說,證書可以在阿里云、騰訊云這種云服務(wù)上申請。申請下來之后,證書只能用于指定的域名和服務(wù)器上。

? netty有提供SSL加密的工具包,只需要通過添加SslHandler,就能快速搭建?;谏厦娴拇a,我們重新定義一個ChannelInitializer。

public class SSLChannelInitializer extends ChannelInitializer<SocketChannel> {

    private final SslContext sslContext;

    public SSLChannelInitializer() {
        String keyStoreFilePath = "/root/.ssl/test.pkcs12";
        String keyStorePassword = "Password@123";

        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(new FileInputStream(keyStoreFilePath), keyStorePassword.toCharArray());

            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());

            sslContext = SslContextBuilder.forServer(keyManagerFactory).build();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void initChannel(SocketChannel ch) {
        ChannelPipeline pipeline = ch.pipeline();
        SSLEngine sslEngine = sslContext.newEngine(ch.alloc());
        pipeline
                .addLast(new SslHandler(sslEngine))
                  .addLast("decoder", new HttpRequestDecoder())
                  .addLast("encoder", new HttpResponseEncoder())
                  .addLast("aggregator", new HttpObjectAggregator(512 * 1024))
                  .addLast("handler", new HttpHandler());
        ;
    }
}

以上就是我通過netty做http服務(wù)器demo的全部代碼和剖析,希望這篇文章能幫到你,有問題評論區(qū)溝通。

Netty入門教程3——Decoder和Encoder
Netty入門教程4——如何實現(xiàn)長連接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,533評論 19 139
  • Netty是一個高性能事件驅(qū)動的異步的非堵塞的IO(NIO)框架,用于建立TCP等底層的連接,基于Netty可以建...
    我是解憂鴨鋪鴨閱讀 1,454評論 0 2
  • netty常用API學習 netty簡介 Netty是基于Java NIO的網(wǎng)絡(luò)應(yīng)用框架. Netty是一個NIO...
    花丶小偉閱讀 6,118評論 0 20
  • 第一部分Netty的概念及體系結(jié)構(gòu) Netty是一款用于創(chuàng)建高性能網(wǎng)絡(luò)應(yīng)用程序的高級框架。在第一部分,我們將深入地...
    全能程序猿閱讀 231,422評論 4 46
  • 一天天過得真快,日子在美食與守望里溜走。充實而悠閑,緊張又松弛,期待與渴望…… 2017年9月14日 晴 星期四 ...
    旅京媽媽閱讀 711評論 1 6

友情鏈接更多精彩內(nèi)容