Netty基于Http协议的服务端开发

简单案例

先照常写一个Handler,注意收到的是HttpObject(实际上是一个DefaultHttpRequest)类型。它附带了客户端的请求信息(uri、请求method、请求头)

public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    //读取事件
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if(msg instanceof HttpRequest){
            System.out.println("msg 类型="+msg.getClass());
            System.out.println("客户端地址" + ctx.channel().remoteAddress());
            //构建HttpResponse
            ByteBuf content = Unpooled.copiedBuffer("hello,我是服务器", CharsetUtil.UTF_8);
            DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
            //设置响应头
            response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain; charset=utf-8");
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());
            //返回response
            ctx.writeAndFlush(response);
        }
    }
}

在ChannelInitializer这里,为channel的pipeline添加上面写的handler。注意在前面添加一个HttpServerCodec,是Netty提供的基于HTTP的编解码器。

public class TestServerInitializer extends ChannelInitializer<SocketChannel> {
    protected void initChannel(SocketChannel ch) throws Exception {
        //向管道加入处理器
        //得到管道
        ChannelPipeline pipeline = ch.pipeline();
        //加入netty提供的httpServerCodec (coder+decoder)
        //基于HTTP的编解码器
        pipeline.addLast("MyHttpServerCodec",new HttpServerCodec());
        pipeline.addLast("MyTestHttpServerHandler",new TestHttpServerHandler());
    }
}

启动服务端的代码:

public class TestHttpServer {
    public void bind(int port) throws Exception{
        //配置服务器的NIO线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try{
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new TestServerInitializer());
            System.out.println("---------服务器正在启动---------");
            ChannelFuture future = serverBootstrap.bind(port).sync();
            //等待服务端监听端口关闭
            future.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        new TestHttpServer().bind(8080);
    }
}

这样一个简单案例就实现了,只要客户端向该服务端的主机、端口发起HTTP请求,就会收到对应的响应,而由于没有判断请求方法、uri,无论什么请求都会收到一样的响应。

对请求资源(uri)进行过滤

浏览器在对某网站发起get请求的时候,通常还会发起对该网站/favicon.ico的请求,如果我们不想对这个请求做出404回应,可以这样写:

public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    //读取事件
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if(msg instanceof HttpRequest){
            System.out.println("msg 类型="+msg.getClass());
            System.out.println("客户端地址" + ctx.channel().remoteAddress());
            //获取到请求URI
            HttpRequest httpRequest = (HttpRequest) msg;
            URI uri = new URI(httpRequest.uri());
            if("/favicon.ico".equals(uri.getPath())){
                System.out.println("请求了图标,返回404");
                ByteBuf error = Unpooled.copiedBuffer("该资源不存在", CharsetUtil.UTF_8);
                DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND, error);
                response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain; charset=utf-8");
                response.headers().set(HttpHeaderNames.CONTENT_LENGTH,error.readableBytes());
                ctx.writeAndFlush(response);
                return;
            }
            //构建HttpResponse
            ByteBuf content = Unpooled.copiedBuffer("hello,我是服务器", CharsetUtil.UTF_8);
            DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
            response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain; charset=utf-8");
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());
            //返回response
            ctx.writeAndFlush(response);
        }
    }
}

这里通过HttpRequest来获取了客户端请求的uri,除了uri外,通过HttpRequest还可以获取:

  • httpRequest.method():获取请求方法,这些方法在HttpMethod中被枚举
  • httpRequest.protocolVersion():获取协议版本,是HttpVersion.HTTP_1_0HttpVersion.HTTP_1_1
  • httpRequest.headers():请求头,可读可写,使用get/set方法

其他handler

在简单案例中只用了一个HttpServerCodec作为http编解码器,还有一些自带的handler可以使用:

  • HttpObjectAggregator,对于POST请求存在请求体,HTTP数据在传输过程中是分段传输的,HttpObjectAggregator可以将多个段聚合。
  • ChunkedWriteHandler,在简单案例中,我们用了响应头的CONTENT_LENGTH来指定响应体的长度,而有的时候无法确定信息大小,就可以使用Chunked编码传输(分块)。

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/netty%e5%9f%ba%e4%ba%8ehttp%e5%8d%8f%e8%ae%ae%e7%9a%84%e6%9c%8d%e5%8a%a1%e7%ab%af%e5%bc%80%e5%8f%91/

发表评论

电子邮件地址不会被公开。