• Netty 应用:Http服务器


    netty作为http服务器的服务端代码清单,实际开发中可拆分为netty自带的处理器initializer,自定义处理器handler两种,便于区分。

    服务端实现

    /**
     * Created by fubin on 2019/7/10.
     * curl -X POST http://localhost:8899
     */
    public class HttpServer {
        public static void main(String[] args) throws Exception {
            //两个死循环,联想到Tomcat和操作系统的设计
            EventLoopGroup bossGroup = new NioEventLoopGroup();//接收连接
            EventLoopGroup workerGroup = new NioEventLoopGroup();//处理连接
            try{
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.group(bossGroup,workerGroup)
                        .channel(NioServerSocketChannel.class)
                        //子处理器,自己编写的
                        .childHandler(new HttpServerInitializer());
    
                ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
                channelFuture.channel().closeFuture().sync();
            }finally {
                //优雅关闭
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    
    }
    /**
     * 初始化netty自带的处理器
     */
    class HttpServerInitializer extends ChannelInitializer<SocketChannel>{
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            //一个管道,里面有很多拦截器,做业务处理
            ChannelPipeline pipeline = socketChannel.pipeline();
            //http处理器:http编解码的封装
            pipeline.addLast("httpServerCodec",new HttpServerCodec());
            pipeline.addLast("httpServerHandler",new HttpServerHandler());
        }
    }
    /**
     * 自己定义的处理器
     */
    class HttpServerHandler extends SimpleChannelInboundHandler<HttpObject>{
        //读取客户端发送的请求,并且向客户端返回响应
        protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject httpObject) throws Exception {
            //io.netty.handler.codec.http.DefaultHttpRequest
            System.out.println(httpObject.getClass());
            ///0:0:0:0:0:0:0:1:64406
            System.out.println(channelHandlerContext.channel().remoteAddress());
            Thread.sleep(8000);
            //不加服务端会抛异常
            if(httpObject instanceof HttpRequest){
                HttpRequest httpRequest = (HttpRequest)httpObject;
                System.out.println("请求方法名:"+httpRequest.method().name());
    
                URI uri = new URI(httpRequest.uri());
                if("/favicon.ico".equals(uri.getPath())){
                    System.out.println("请求favicon.ico");
                    return;
                }
    
                //构造向客户端返回的字符串
                ByteBuf content = Unpooled.copiedBuffer("helloworld", CharsetUtil.UTF_8);
                //支持http响应的对象
                FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK,content);
                //设置http头信息
                response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
                response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());
                //响应发送给客户端
                channelHandlerContext.writeAndFlush(response);
                //主动关闭,会马上调用失效和注销事件
                channelHandlerContext.channel().close();
            }
        }
    }

    网站图标

    浏览器会先查找网页所在目录是否存在名为favicon.ico的文件,如不存在则去网站根目录再次查找,若再找不到则认为不存在。

    所以,当我使用curl请求测试时,请求只执行一次,而从浏览器执行则请求两次,一次为http://localhost:8899,另一次为http://localhost:8899/favicon.ico

    对于netty程序,curl和浏览器有一些区别

    自定义的handler其余实现方法:注册,激活,注销,失效,handler添加等

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel 激活");
    }
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel 注册");
    }
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel 失效");
    }
    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel 注销");
    }
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handler 添加");
    }

    如果是基于http1.1,在keepalive默认三秒后还没再次发请求(可以设置),服务器端会主动关闭请求,在http1.0是短连接,会马上关闭。

    //在channelRead0调用主动关闭,会马上调用失效和注销事件
    channelHandlerContext.channel().close();

    HttpObject对象

    //io.netty.handler.codec.http.DefaultHttpRequest
    System.out.println(httpObject.getClass());

    远程端口

    ///0:0:0:0:0:0:0:1:64406
    System.out.println(channelHandlerContext.channel().remoteAddress());

    mac上查看tcp端口的命令lsoflist open file

    Http服务器编解码组合实现 HttpServerCodec 的API文解释

    HttpRequestDecoderHttpResponseEncoder的组合,可以更轻松地实现服务器端HTTP。

    • HttpRequestDecoder API

    ByteBufs解码为HttpRequestsHttpContents
    防止过多内存消耗的参数

    参数名含义
    maxInitialLineLength (e.g. “GET / HTTP/1.0”)初始行的最大长度,超过这个长度就会引发TooLongFrameException异常。
    maxHeaderSize 所有消息头的最大长度。如果每个头的长度之和超过此值,将引发TooLongFrameException。
    maxChunkSize 内容或每个块的最大长度。如果内容长度超过此值,则解码请求的传输编码将转换为“分块”,内容将被分割为多个HttpContents。如果HTTP请求的传输编码已经“分块”,如果块的长度超过这个值,每个块将被分割成更小的块。如果您不喜欢在处理程序中处理HttpContents,请在ChannelPipeline中的这个解码器之后插入HttpObjectAggregator
    • HttpObjectAggregator api doc

    一个ChannelHandler,它将HttpMessage及其后续的HttpContents聚合到一个FullHttpRequest或FullHttpResponse中(取决于它是否用于处理请求或响应),没有后续的HttpContents。 当您不想处理传输编码为“chunked”的HTTP消息时,它非常有用。 如果用于处理响应,则在ChannelPipeline中的HttpResponseDecoder之后插入此处理程序,或者如果用于处理请求,则在ChannelPipeline中的HttpRequestDecoder和HttpResponseEncoder之后插入此处理程序。

    • ChunkedWriteHandler api doc

    一个ChannelHandler,它增加了对异步写入大数据流的支持,既不花费大量内存也不获取OutOfMemoryError。诸如文件传输之类的大数据流需要在ChannelHandler实现中进行复杂的状态管理。 ChunkedWriteHandler管理这些复杂的状态,以便您可以毫无困难地发送大型数据流。

    要在应用程序中使用ChunkedWriteHandler,您必须插入一个新的ChunkedWriteHandler实例:

    ChannelPipeline p = ...;
    p.addLast("streamer", new ChunkedWriteHandler());
    p.addLast("handler", new MyHandler());

    一旦插入,你可以写一个ChunkedInput,这样ChunkedWriteHandler就可以把它捡起来,然后一个块一个块地获取流的内容,然后把获取的块写到下游:

    Channel ch = ...;
    ch.write(new ChunkedFile(new File("video.mkv"));

    发送间歇性生成块的流
    某些ChunkedInput会在特定事件或时间上生成一个块。此类ChunkedInput实现通常在ChunkedInput.readChunk(ChannelHandlerContext)上返回null,从而导致无限期暂停传输。要在新块可用时恢复传输,您必须调用resumeTransfer()。

    ChannelPipeline p = ...;
    ...
    p.addLast("decoder", new HttpRequestDecoder());
    p.addLast("encoder", new HttpResponseEncoder());
    p.addLast("aggregator", new HttpObjectAggregator(1048576));
    ...
    p.addLast("handler", new HttpRequestHandler());

    为方便起见,请考虑在HttpObjectAggregator之前放置一个HttpServerCodec,因为它既可以作为HttpRequestDecoder又可以作为HttpResponseEncoder。

    请注意,HttpObjectAggregator最终可能会发送一个HttpResponse:

    响应状态什么时机发送
    100-continue 收到’100-continue’期望值,’content-length’不超过maxContentLength
    417 Expectation Failed 收到’100-continue’期望值,’content-length’超过maxContentLength
    413 Request Entity Too Large 到目前为止,’content-length’或接收的字节数超过maxContentLength

     

  • 相关阅读:
    QT学习笔记
    局域网摄像头安装与调试
    从0开始搭建视觉检测智能车
    树莓派安装anaconda
    手把手教你搭建视觉检测智能车
    树莓派与Arduino串口通信实验
    树莓派设置关机重启键
    树莓派can通信
    树莓派GPIO使用笔记
    MySQL练习题
  • 原文地址:https://www.cnblogs.com/fubinhnust/p/11940595.html
Copyright © 2020-2023  润新知