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

 

原文地址:https://www.cnblogs.com/fubinhnust/p/11940595.html