使用Netty实现Http服务器及客户端 - Go语言中文社区

使用Netty实现Http服务器及客户端


HTTP 是基于请求/响应模式的:客户端向服务器发送一个HTTP 请求,然后服务器将会返回一个HTTP 响应。


一个HTTP 请求/响应可能由多个数据部分组成,并且它总是以一个LastHttpContent 部分作为结束。FullHttpRequest 和 FullHttpResponse 消息是特殊的子类型,分别代表了完整的请求和响应。 所有类型的HTTP 消息(FullHttpRequest、LastHttpContent等等)都实现了HttpObject 接口。
在这里插入图片描述

在了解完 Netty的编解码器框架 后,我们大概清除了编解码器的作用,这里我们就需要用到有关Http系统的几个编解码器:

如在客户端,我们肯定要将发送的请求进行编码,在接收到服务端的响应时需要将其解码
HttpRequestEncoder: 将 HttpRequest、HttpContent 和 LastHttpContent 消息编码为字节
HttpResponseDecoder: 将字节解码为HttpResponse、HttpContent 和LastHttpContent 消息
同时这两个我们也可以用一个来代替:HttpClientCodec

服务端,我们需要将接收到的客户端请求进行解码,在给客户端发送响应信息有需要编码
HttpRequestDecoder: 将字节解码为HttpRequest、HttpContent 和LastHttpContent 消息
HttpResponseEncoder : 将HttpResponse、HttpContent 和LastHttpContent 消息编码为字节
其同样也是可以直接使用一个来替代:HttpServerCodec


另外我们在上图中也看到,一个HTTP 请求/响应可能由多个数据部分组成,这里为了方便处理,我们一般会进行聚合它们以形成完整的消息。将多个消息部分合并为 FullHttpRequest 或者 FullHttpResponse 消息。通过这样的方式,你将总是看到完整的消息内容 —— HttpObjectAggregator


另外当使用HTTP 时,建议开启压缩功能以尽可能多地减小传输数据的大小。虽然压缩会带来一些CPU 时钟周期上的开销,但是通常来说它都是一个好主意,特别是对于文本数据来说。Netty 为压缩和解压缩提供了ChannelHandler 实现,它们同时支持 gzip 和 deflate 编码。

一般在服务器端会进行压缩 —— HttpContentCompressor,客户端接收到信息后进行解压 —— HttpContentDecompressor




接下来,我们就来实现一个简单的Http服务器,首先看一下Http服务器的启动相关

public class HttpServer {
    
    public static final int PORT = 8888;
    
    public static EventLoopGroup group = new NioEventLoopGroup();
    public static ServerBootstrap serverBootstrap = new ServerBootstrap();

    public static void main(String[] args) throws Exception {
        try {
            serverBootstrap.group(group)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ServerHandlerInit());

            ChannelFuture channelFuture = serverBootstrap.bind(PORT).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();
        }
    }
}

其实这里和我们一开始介绍的Netty小应用很类似,几乎是相同的,只不过我们将其需要添加的 ChannelHandler 提取出来,单独的写了一个类,如下:

public class ServerHandlerInit extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast("responseEncode", new HttpResponseEncoder());
        pipeline.addLast("requestDecode", new HttpRequestDecoder());

        pipeline.addLast("objectAggregator", new HttpObjectAggregator(1024));
        pipeline.addLast("contentCompressor", new HttpContentCompressor());

        pipeline.addLast("httpServerHandler", new HttpServerHandler());
    }
}

这其中就是添加了刚刚我们介绍的一些编解码器的框架,最后一个HttpServerHandler就是我们自己实现的,用于对Http请求的处理,内容如下:

public class HttpServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 收到消息是,返回响应信息
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        FullHttpRequest httpRequest = (FullHttpRequest)msg;
        String ret = "";
        try {
            String uri = httpRequest.uri();
            String data = httpRequest.content().toString(CharsetUtil.UTF_8);
            HttpMethod method = httpRequest.method();
            if(!"/bxs".equalsIgnoreCase(uri)){
                ret = "非法请求路径:" + uri;
                response(ret, ctx, HttpResponseStatus.BAD_REQUEST);
                return;
            }

            if(HttpMethod.GET.equals(method)){
                System.out.println("客户端请求数据内容:" + data);
                ret = "服务端接受到数据,接收到数据为:" + data;
                response(ret, ctx, HttpResponseStatus.OK);
            }
            if (HttpMethod.POST.equals(method)){
                //...
            }
            if (HttpMethod.PUT.equals(method)){
                //..
            }
            //..
        } catch (Exception e) {
            System.out.println("服务器处理失败...");
        } finally {
            httpRequest.release();
        }
    }

    private void response(String data, ChannelHandlerContext ctx, HttpResponseStatus status){
        FullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, 
                Unpooled.copiedBuffer(data, CharsetUtil.UTF_8));
        resp.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=UTF-8");
        ctx.writeAndFlush(resp).addListener(ChannelFutureListener.CLOSE);
    }
}

这其中我们还限制了只有 /bxs 的访问路径可以进行访问,然后就可以对不同类型的Http请求进行处理,这里我们简单的响应一句字符串,这样一个超简单的Http服务就完成了,测试结果如下:
在这里插入图片描述
在这里插入图片描述




上述我们完成了一个简单的Http服务器,然后使用了浏览器进行测试,这里我们还可以使用Netty来实现一个Http的客户端,如下:

public class HttpClient {

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

    private void connect(String host, int port) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();

        try {
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.SO_KEEPALIVE, true);

            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {

                    ch.pipeline().addLast(new HttpClientCodec());

                    ch.pipeline().addLast("objectAggregator", new HttpObjectAggregator(2014));
                    ch.pipeline().addLast("contentDecompressor", new HttpContentDecompressor());

                    ch.pipeline().addLast("httpClientHandler", new HttpClientHandler());
                }
            });
            ChannelFuture channelFuture = bootstrap.connect(host, port).sync();

            URI uri = new URI("/bxs");
            String data = "Hello Netty";
            DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
                    uri.toASCIIString(), Unpooled.wrappedBuffer(data.getBytes("UTF-8")));

            req.headers().set(HttpHeaderNames.HOST, host);
            req.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
            req.headers().set(HttpHeaderNames.CONTENT_LENGTH, req.content().readableBytes());

            channelFuture.channel().write(req);
            channelFuture.channel().flush();
            channelFuture.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();
        }
    }
}

上述代码中一开始的部分都是大同小异的,连接上服务器后,我们就开始给服务器发送Http请求,然后我们主要看一下客户端在接受服务器响应后的处理——HttpClientHandler

public class HttpClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        FullHttpResponse httpResponse = (FullHttpResponse)msg;
        System.out.println(httpResponse.headers());

        ByteBuf content = httpResponse.content();
        System.out.println(content.toString(CharsetUtil.UTF_8));
        content.release();
    }
}

这里我们就是简单的将服务器返回的消息头及内容打印出来,其测试结果如下:
在这里插入图片描述
在这里插入图片描述




SSL和TLS这样的安全协议,它们层叠在其他协议之上,用以实现数据安全。我们在访问安全网站时遇到过这些协议,但是它们也可用于其他不是基于HTTP的应用程序,如安全SMTP(SMTPS)邮件服务器甚至是关系型数据库系统。


为了支持SSL/TLS,Java 提供了javax.net.ssl 包,它的SSLContext 和SSLEngine类使得实现解密和加密相当简单直接。Netty 通过一个名为 SslHandler 的ChannelHandler实现利用了这个API,其中SslHandler 在内部使用SSLEngine 来完成实际的工作。


其在代码中的使用也是非常的简单,这里我们将之前实现的 Http 服务器做一点点的修改,如下:
在这里插入图片描述


在大多数情况下,SslHandler 将是ChannelPipeline 中的第一个ChannelHandler。
在这里插入图片描述

然后我们发下在浏览器中使用 http:// 进行访问就不可以了,这里就比如使用 https:// 进行访问
在这里插入图片描述
在这里插入图片描述


另外Netty 还提供了使用 OpenSSL 工具包(www.openssl.org)的SSLEngine 实现。这个OpenSslEngine 类提供了比JDK 提供的SSLEngine 实现更好的性能。


如果OpenSSL库可用,可以将Netty 应用程序(客户端和服务器)配置为默认使用OpenSslEngine。如果不可用,Netty 将会回退到JDK 实现。

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/newbie0107/article/details/104401004
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-04-18 23:32:34
  • 阅读 ( 1025 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢