构建HTTP(HTTPS)应用程序

  为了支持 SSL/TLS,Java提供javax.net.ssl 包,它的 SSLContext 和 SSLEngine 类使得解密和加密相当简单和高效。SSLContext是SSL链接的上下文,SSLEngine主要用于出站和入站字节流的操作。

  Netty还提供了使用 OpenSSL工具包的SSLEngine实现,该SSLEngine比JDK提供的SSLEngine实现有更好的性能。

  

  Netty通过一个名为SslHandlerChannelHandler实现加密和解密的功能,其中SslHandler在内部使用SSLEngine来完成实际的工作,SSLEngine的实现可以是JDK的SSLEngine,也可以是 Netty 的OpenSslEngine,当然推荐使用Netty的OpenSslEngine,因为性能更好。

  大多数情况下,SslHandler 将是 ChannelPipeline 中的第一个 ChannelHandler。只有在所有其他的 ChannelHandler 将它们的逻辑应用到数据之后,才会进行加密。

Netty为HTTP消息提供编码器和解码器:

  • HttpRequestEncoder: 编码器:用于客户端,向服务器发送请求
  • HttpResponseEecoder: 编码器:用于服务端,向客户端发送响应
  • HttpRequestDecoder:解码器:用于服务端,接收来自客户端的请求
  • HttpResponseDecoder: 解码器:用于客户端,接收来自服务端的请求

  编解码器

  • HttpClientCodec: 用于客户端的编解码器,等效于 HttpRequestEncoderHttpResponseDecoder的组合
  • HttpServerCodec:用于服务端的编解码器,等效于 HttpRequsetDecoder和 HttpResponseEncoder的组合

  HttpServerCodec 同时实现了 ChannelInboundHandler和 ChannelOutboundHandler接口,以达到同时具有编码和解码的能力。

  聚合器

  HttpObjectAggregator: 聚合器,可以将多个消息部分合并为 FullHttpRequest或者 FullHttpResponse消息。使用该聚合器的原因是HTTP解码器会在每个HTTP消息中生成多个消息对象,如HttpRequest/HttpResponse,HttpContent,LastHttpContent,使用聚合器将它们聚合成一个完整的消息内容,这样就不用关心消息碎片了。

  服务端:

public class HttpsServer {

    public static void main(String[] args) {
        //使用Netty自带的证书工具生成一个数字证书
        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup worker = new NioEventLoopGroup();
        try {
            SelfSignedCertificate certificate = new SelfSignedCertificate();
            SslContext sslContext = SslContextBuilder.forServer(certificate.certificate(), certificate.privateKey()).build();

            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            if (sslContext != null) {
                                pipeline.addLast(sslContext.newHandler(ch.alloc()));
                            }
                            //添加一个HTTP的编解码器
                            pipeline.addLast(new HttpServerCodec());
                            //添加HTTP消息聚合器
                            pipeline.addLast(new HttpObjectAggregator(64 * 1024));
                            //添加一个自定义服务端Handler
                            pipeline.addLast(new HttpsServerHandler());
                        }
                    });
            ChannelFuture future = bootstrap.bind(8089).sync();
            future.channel().closeFuture().sync();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (SSLException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }
}

      自定义handler

public class HttpsServerHandler extends SimpleChannelInboundHandler<HttpObject> {

    private static final AsciiString CONTENT_TYPE = AsciiString.cached("Content-Type");
    private static final AsciiString CONTENT_LENGTH = AsciiString.cached("Content-Length");
    private static final AsciiString CONNECTION = AsciiString.cached("Connection");
    private static final AsciiString KEEP_ALIVE = AsciiString.cached("keep-alive");

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if (msg instanceof HttpRequest) {
            String content = "";
            HttpRequest request = (HttpRequest) msg;
            if ("/hello".equals(request.getUri())) {
                content = "hello world";
                response2Client(ctx, request, content);
            } else {
                content = "Connect the Server";
                response2Client(ctx, request, content);
            }
        }
    }

    private void response2Client(ChannelHandlerContext ctx, HttpRequest request, String content) {
        boolean keepAlive = HttpUtil.isKeepAlive(request);
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(content.getBytes()));
        response.headers().set(CONTENT_TYPE, "text/plain");
        response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());
        if (!keepAlive) {
            ctx.write(response).addListener(ChannelFutureListener.CLOSE);
        } else {
            response.headers().set(CONNECTION, KEEP_ALIVE);
            ctx.write(response);
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

AsciiString

    AsciiString提供实现CharSequence 接口的 AsciiString,至于 CharSequence就是 String的父类。AsciiString 包含的字符只占1个字节,当处理 US-ASCII 或者 ISO-8859-1 字符串时可以节省空间。例如,HTTP编解码器使用 AsciiString处理 header name ,因为将AsciiString编码到 ByteBuf中不会有类型转换的代价,其内部实现就是用的 byte,对于String来说,内部是存 char[],使用 String就需要将 char转换成 byte,所以AsciiString 比String类型有更好的性能。

      参考:

   https://pjmike.github.io/2018/09/25/Netty%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0%E4%B9%8B%E6%9E%84%E5%BB%BAHTTP-HTTPS-%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F/

posted on 2019-12-01 20:36  溪水静幽  阅读(354)  评论(0)    收藏  举报