【Netty】实战:入门案例 之 HTTP开发

1 案例需求分析

  • Netty 服务器在 6668 端口监听,浏览器发出请求 http://localhost:6668/
  • 服务器可以回复消息给客户端 Hello! 我是服务器 , 并对特定请求资源进行过滤
  • 目的:了解如何使用Netty做HTTP服务开发,并且理解 Handler 实例和客户端及其请求的关系。

2 代码实现

2.1 总体思路

代码总体实现思路说明

  • 步骤一:创建两个线程组 BossGroup 和 WorkerGroup,他们的类型都是 NioEventLoopGroup。bossGroup 只处理连接请求,workerGroup 处理客户端业务。
  • 步骤二:创建服务器端启动对象 ServerBootstrap ,并进行参数配置:
    • 设置 BossGroup 和 WorkerGroup
    • 设置使用使用 NioSocketChannel 作为服务器的通道实现
    • 设置保持活动连接
    • 创建一个 通道(pipline) 测试对象(匿名对象),并为 pipline设置一个 Handler
  • 步骤三:绑定端口并且同步,启动服务器,生成并返回一个 ChannelFuture 对象
  • 步骤四:设置对关闭通道事件进行监听
public class NettyHttpServer {
    
    
    public static void main(String[] args) {
    
    
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(8);
        try {
    
    
            // 2.创建服务器端启动对象,配置参数
            ServerBootstrap bootstrap = new ServerBootstrap();
            // 使用链式编程进行设置
            bootstrap.group(bossGroup,workerGroup)// 设置两个线程组
                    .channel(NioServerSocketChannel.class)// 使用 NioSocketChannel 作为服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG,128)// 设置线程队列得到连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE,true)// 设置保持活动连接
                    // 给 WorkerGroup 的 EventLoop 对应的 管道 设置 Handler
                    .childHandler(new NettyHttpServerInitializer());
            System.out.println("服务器就绪...");
            // 3.绑定端口并且同步,生成一个 ChannelFuture 对象
            ChannelFuture cf = bootstrap.bind(8848).sync();
            // 给 cf 注册监听器,监控我们关心的事件
            cf.addListener(new ChannelFutureListener() {
    
    
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
    
    
                    if (cf.isSuccess()) {
    
    
                        System.out.println("监听端口 8848 成功");
                    } else {
    
    
                        System.out.println("监听端口 8848 失败");
                    }
                }
            });

            // 4.对关闭通道事件进行监听
            cf.channel().closeFuture().sync();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

在上面的代码中我们将为pipline绑定Handler的代码逻辑抽取成一个类:

public class NettyHttpServerInitializer extends ChannelInitializer<SocketChannel> {
    
    

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
    
    
        // 向 pipline 加入 Handler
        ChannelPipeline pipeline = ch.pipeline();

        // 加入一个 netty 提供的 httpServerCodec
        // httpServerCodec:netty 提供的http编解码器
        pipeline.addLast("MyHttpServerCodec",new HttpServerCodec());
        // 增加一个自定义Handler
        pipeline.addLast("MyNettyHttpServerHandler",new NettyHttpServerHandler());
    }
}

上面这种写法和下面的写法是一样的

bootstrap.group(bossGroup,workerGroup)// 设置两个线程组
         .channel(NioServerSocketChannel.class)// 使用 NioSocketChannel 作为服务器的通道实现
         .option(ChannelOption.SO_BACKLOG,128)// 设置线程队列得到连接个数
         .childOption(ChannelOption.SO_KEEPALIVE,true)// 设置保持活动连接
         // 给 WorkerGroup 的 EventLoop 对应的 管道 设置处理器
         .childHandler(new ChannelInitializer<SocketChannel>() {
    
    // 创建一个通道测试对象(匿名对象)
             // 向 pipline 设置 处理器
             @Override
             protected void initChannel(SocketChannel socketChannel) throws Exception {
    
    
                 // 向 pipline 加入 Handler
		         ChannelPipeline pipeline = ch.pipeline();
		
		         // 加入一个 netty 提供的 httpServerCodec
		         // httpServerCodec:netty 提供的http编解码器
		         pipeline.addLast("MyHttpServerCodec",new HttpServerCodec());
		         // 增加一个自定义Handler
		         pipeline.addLast("MyNettyHttpServerHandler",new NettyHttpServerHandler());
             }
         });

这里我们为pipline绑定了两个Handler:

  • httpServerCodec:netty 提供的http编解码器;
  • NettyHttpServerHandler:我们自己实现的Handler,用于接收和响应Http请求

NettyHttpServerHandler实现逻辑

它的主要逻辑就是接收客户端发来的请求,然后将信心封装为HttpRespose,返回给客户端。

/*
1.SimpleChannelInboundHandler是ChannelInboundHandlerAdapter的子类
2.HttpObject表示客户端和服务器端相互通讯的数据被封装成 HttpObject 类型

 */
public class NettyHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    
    

    // 读取客户端数据
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
    
    
        // 判断 msg 是不是 httprequest 请求
        if (msg instanceof HttpRequest) {
    
    
            System.out.println("msg 类型:"+msg.getClass());
            System.out.println("客户端地址:"+ctx.channel().remoteAddress());
            // 给浏览器回复信息 [http协议]
            ByteBuf content = Unpooled.copiedBuffer("hello,我是服务器...", CharsetUtil.UTF_16);
            // 构造http响应,httpresponse
            DefaultHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
            httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plaini");
            httpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());
            // 发送构建好的 response
            ctx.writeAndFlush(httpResponse);
        }
    }
}

2.2 测试结果问题分析

  • 首先启动程序
    在这里插入图片描述
  • 浏览器发送请求:http://localhost:8848/
    在这里插入图片描述
  • 查看控制台打印结果,我们发现服务端接收到了两次请求
    在这里插入图片描述

问题分析

我们打开浏览器控制台发现,其实浏览器发送了两次请求,因此我们需要对特定请求资源进行过滤
在这里插入图片描述

2.3 问题解决:对特定请求资源进行过滤

我们只需要对 http://localhost:8848/favicon.ico 请求进行过滤即可。

/*
1.SimpleChannelInboundHandler是ChannelInboundHandlerAdapter的子类
2.HttpObject表示客户端和服务器端相互通讯的数据被封装成 HttpObject 类型

 */
public class NettyHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    
    

    // 读取客户端数据
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
    
    
        // 判断 msg 是不是 httprequest 请求
        if (msg instanceof HttpRequest) {
    
    
            System.out.println("msg 类型:"+msg.getClass());
            System.out.println("客户端地址:"+ctx.channel().remoteAddress());
            // 获取 httprequest
            HttpRequest httpRequest = (HttpRequest) msg;
            // 获取 uri
            URI uri = new URI(httpRequest.uri());
            // 判断
            if ("/favicon.ico".equals(uri.getPath())) {
    
    
                System.out.println("不做响应...");
                return;
            }
            // 给浏览器回复信息 [http协议]
            ByteBuf content = Unpooled.copiedBuffer("hello,我是服务器...", CharsetUtil.UTF_16);
            // 构造http响应,httpresponse
            DefaultHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
            httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plaini");
            httpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());
            // 发送构建好的 response
            ctx.writeAndFlush(httpResponse);

        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_36389060/article/details/124513981