Netty笔记之二:使用Netty构建http服务
先直接看demo,demo实现功能:
使用netty构建一个类似于tomcat的web服务器,服务端监听8899端口,当访问8899端口的时候,服务器端给客户端hello world的响应。
直接看代码:
服务端:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class TestServer {
public static void main(String[] args) throws Exception{
//bossGroup是获取连接的,workerGroup是用来处理连接的,这二个线程组都是死循环
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
//简化服务端启动的一个类
ServerBootstrap serverBootstrap = new ServerBootstrap();
//group有二个重载方法,一个是接收一个EventLoopGroup类型参数的方法,一个是接收二个EventLoopGroup类型的参数的方法
serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).
childHandler(new TestServerInitializer());
ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
服务端HttpServerInitializer(初始化连接的时候执行的回调),处理器Handler构成了一个链路
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
public class TestServerInitializer extends ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//http协议的编解码使用的,是HttpRequestDecoder和HttpResponseEncoder处理器组合
//HttpRequestDecoder http请求的解码
//HttpResponseEncoder http请求的编码
pipeline.addLast("httpServerCodec",new HttpServerCodec());
pipeline.addLast("testHttpServerHandler",new TestHttpServerHandler());
}
}
服务端Handler,对请求进行路由
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import java.net.URI;
//浏览器的特性会发送一个/favicon.ico请求,获取网站的图标
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject>{
//channelRead0是读取客户端的请求并且向客户端返回响应的一个方法
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
if(msg instanceof HttpRequest){
HttpRequest httpRequest = (HttpRequest)msg;
System.out.println("请求方法名:"+httpRequest.method().name()); //get方法
URI uri = new URI(httpRequest.uri());
//使用浏览器访问localhost:8899会发送二次请求,其中有一次是localhost:8899/favicon.ico,这个url请求访问网站的图标
if("/favicon.ico".equals(uri.getPath())){
System.out.println("请求favicon.ico");
return;
}
//向客户端返回的内容
ByteBuf content = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8);
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,content);
response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());
ctx.writeAndFlush(response);
//其实更合理的close连接应该判断是http1.O还是1.1来进行判断请求超时时间来断开channel连接。
ctx.channel().close();
}
}
}
使用curl命令访问该服务:
➜ curl 'localhost:8899'
hello world%
控制台打印:
请求方法名:GET
相应的使用post,put,delete请求服务
post请求
curl -X post 'localhost:8899'
put请求
curl -X put 'localhost:8899'
delete请求
curl -X delete 'localhost:8899'
我们使用谷歌浏览器访问localhost:8899
,打开开发者工具的时候发现还会请求一次/favicon.ico
(请求当前网站的图标)。此时控制台打印
请求方法名:GET
请求方法名:GET
请求favicon.ico
扩展
我们改造一下TestHttpServerHandler
代码,因为netty是事件驱动的网络框架,我们定义的TestHttpServerHandler
继承SimpleChannelInboundHandler
类,SimpleChannelInboundHandler
类又继承ChannelInboundHandlerAdapter
类,发现ChannelInboundHandlerAdapter
中定义了好多事件回调方法,在不同的事件触发时会执行相应的方法,我们在TestHttpServerHandler
重写这些方法来梳理一下netty的事件回调流程。
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import java.net.URI;
//浏览器的特性会发送一个/favicon.ico请求,获取网站的图标
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject>{
//channelRead0是读取客户端的请求并且向客户端返回响应的一个方法
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
//io.netty.handler.codec.http.DefaultHttpRequest,io.netty.handler.codec.http.LastHttpContent$1
System.out.println(msg.getClass());
//打印出远程的地址,/0:0:0:0:0:0:0:1: 49734,本地线程的49734端口的线程和netty进行通信
System.out.println(ctx.channel().remoteAddress());
TimeUnit.SECONDS.sleep(8);
if(msg instanceof HttpRequest){
HttpRequest httpRequest = (HttpRequest)msg;
System.out.println("请求方法名:"+httpRequest.method().name()); //get方法
URI uri = new URI(httpRequest.uri());
//使用浏览器访问localhost:8899会发送二次请求,其中有一次是localhost:8899/favicon.ico,这个url请求访问网站的图标
if("/favicon.ico".equals(uri.getPath())){
System.out.println("请求favicon.ico");
return;
}
//向客户端返回的内容
ByteBuf content = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8);
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,content);
response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());
ctx.writeAndFlush(response);
//其实更合理的close连接应该判断是http1.O还是1.1来进行判断请求超时时间来断开channel连接。
ctx.channel().close();
}
}
//管道活跃
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel active");
super.channelActive(ctx);
}
//管道注册
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel registered");
super.channelRegistered(ctx);
}
//通道被添加
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("handler added");
super.handlerAdded(ctx);
}
//管道不活跃了
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel inactive");
super.channelInactive(ctx);
}
//取消注册
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel unregistered");
super.channelUnregistered(ctx);
}
}
使用curl命令访问该服务:
➜ curl 'localhost:8899'
hello world%
控制台显示,我们发现handlerAdded(通道被添加),channelRegistered(通道被注册),channelActive(管道活跃)然后通道关闭之后依次执行channelInactive(管道不活跃了),channelUnregistered(通道取消注册)
handler added
channel registered
channel active
class io.netty.handler.codec.http.DefaultHttpRequest
/0:0:0:0:0:0:0:1:49734
请求方法名:GET
class io.netty.handler.codec.http.LastHttpContent$1
/0:0:0:0:0:0:0:1:49734
channel inactive
channel unregistered
我们发现49734发起了调用8899端口的服务,可以使用命令来查看
➜ lsof -i:8899
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 56283 naeshihiroshi 106u IPv6 0x72f1692aa9c77fbf 0t0 TCP *:8899 (LISTEN)
java 56283 naeshihiroshi 109u IPv6 0x72f1692aaca7cfbf 0t0 TCP localhost:8899->localhost: 49734 (ESTABLISHED)
curl 56649 naeshihiroshi 5u IPv6 0x72f1692aabcfa53f 0t0 TCP localhost: 49734->localhost:8899 (ESTABLISHED)
拓展,使用netty构建restful服务
使用netty,fastjson构建简单的restful服务,参考博客地址
我的代码的github地址,在com.zhihao.miao.netty.restful包下。