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文解释
HttpRequestDecoder
和HttpResponseEncoder
的组合,可以更轻松地实现服务器端HTTP。
- HttpRequestDecoder API
将ByteBufs
解码为HttpRequests
和HttpContents
。
防止过多内存消耗的参数
参数名 | 含义 |
---|---|
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 |