【使用Netty实现Http服务器一】

目标

通过实现HTTP服务器示例,学习Netty中内置的一些Handler,可以帮我们快速的实现一些上层协议。最终我们实现一个简单版本的类似spring mvc的web框架。

创建HTTP Server

创建一个HttpServer的过程与创建TCP服务类似,只不过添加的childHandler不太一样,我们需要添加处理HTTP协议的handler

public class HttpServer {

    public static void main(String[] args) {
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup work = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(boss,work).channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,1024)
                    .childOption(ChannelOption.SO_KEEPALIVE,true)
                    .childOption(ChannelOption.RCVBUF_ALLOCATOR,new AdaptiveRecvByteBufAllocator())
                    .childOption(ChannelOption.TCP_NODELAY,true)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new HttpServerCodec())
                                    .addLast(new ChunkedWriteHandler())
                                    .addLast(new HttpObjectAggregator(64*1024))
                                    .addLast(new MyHttpServerHandler());
                        }
                    });
            ChannelFuture future = serverBootstrap.bind(8080).sync();
            Channel channel = future.channel();
            channel.closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            boss.shutdownGracefully();
            work.shutdownGracefully();
        }
    }
}
  1. 我们将服务端口绑定到8080
  2. 首先添加的是 HttpServerCodec,它包含了对Http协议的编码和解码的功能,也可以分开添加编码(HttpResponseEncoder)和解码(HttpRequestDecoder)
  3. 添加对大数据流的支持:ChunkedWriteHandler
  4. 添加Http消息聚合,因为HTTP允许将大的消息体分割成多个数据块进行传输。因此我们要想直接处理完整的消息内容需要添加这个handler
  5. 添加我们的业务处理handler

Http业务处理

我们首先简单的处理HTTP请求,在后台打印出请求方法和uri,然后返回hello world

public class MyHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
        String methodName = msg.method().name();
        String requestUri = msg.uri();
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer("Hello World".getBytes()));
        response.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/plain");
        System.out.println(String.format("methodName:%s,requestUri:%s", methodName, requestUri));
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
}

这里MyHttpServerHandler 继承类时传入的泛型是FullHttpRequest,这里的类型与你上一个处理的handler有关,我们示例中上一个handler是HttpObjectAggregator,聚合后的数据我们读取就是FullHttpRequest类型了

记得写完数据后要关闭掉链接哦,不然浏览器一直等待

后台打印
浏览器测试
到这里我们已经完成了一个简单的HTTP服务,如果我们要实现一个静态的web服务器,指定一个路径为基地址,通过path访问路径下的html,要如何做呢

实现静态web服务器

实现在运行目录的app目录下作为网站基地址,实现静态网页的访问

public class StaticHttpServer {

    static class Server{
        private String basePath;

        public Server(String basePath) {
            this.basePath = basePath;
        }

        public void start(){
            EventLoopGroup boss = new NioEventLoopGroup();
            EventLoopGroup work = new NioEventLoopGroup();
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.group(boss,work).channel(NioServerSocketChannel.class)
                        .option(ChannelOption.SO_BACKLOG,1024)
                        .childOption(ChannelOption.SO_KEEPALIVE,true)
                        .childOption(ChannelOption.RCVBUF_ALLOCATOR,new AdaptiveRecvByteBufAllocator())
                        .childOption(ChannelOption.TCP_NODELAY,true)
                        .childHandler(new ChannelInitializer<NioSocketChannel>() {
                            @Override
                            protected void initChannel(NioSocketChannel ch) throws Exception {
                                ch.pipeline().addLast(new HttpServerCodec())
                                        .addLast(new ChunkedWriteHandler())
                                        .addLast(new HttpObjectAggregator(64*1024))
                                        .addLast(new MyHttpServerHandler(Server.this.basePath));
                            }
                        });
                ChannelFuture future = serverBootstrap.bind(8080).sync();
                Channel channel = future.channel();
                channel.closeFuture().sync();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                boss.shutdownGracefully();
                work.shutdownGracefully();
            }
        }
    }

    public static void main(String[] args) {
        String basePath = Server.class.getClassLoader().getResource("app").getPath();
        System.out.println(String.format("basePath:%s",basePath));
        Server server = new Server(basePath);
        server.start();
    }

}

创建服务和handler时我们传入一个basePath作为静态网站的基地址,把网页文件放置在里面即可通过path访问。
下面我们看看handler的处理代码

public class MyHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private String basePath;

    public MyHttpServerHandler(String basePath) {
        this.basePath = basePath;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
        String methodName = msg.method().name();
        String requestUri = msg.uri();
        if (!"GET".equals(methodName)) {
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer("Not Support".getBytes()));
            response.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/plain");
            return ;
        }
        this.sendResponse(requestUri,ctx);
    }

    private void sendResponse(String uri,ChannelHandlerContext ctx) throws IOException {
        String filename = "index";
        String path = uri.substring(0,uri.lastIndexOf("/"));
        if (!"/".equals(uri)){
            //请求
            filename = uri.substring(uri.lastIndexOf("/")+1);
        }
        System.out.println(String.format("path:%s,filename:%s", path, filename));
        File file = new File(basePath + path + "/" + filename+".html");
        if (!file.exists()){
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND);
            response.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/plain");
            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
            return;
        }
        //读取文件到buf中返回
        RandomAccessFile accessFile = new RandomAccessFile(file,"r");
        FileChannel fileChannel = accessFile.getChannel();
        ByteBuf buf = Unpooled.wrappedBuffer(fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, file.length()));
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,buf);
        response.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/html");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
}

我们通过请求uri,获取到要访问的路径和文件,然后把文件数据作为html文档格式返回。如果未找到则返回404
网页文件
访问结果

  • 35
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ThinkLess404

有问题可以私信交流

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值