上文《Netty挖掘机(二)初识Netty》,主要介绍了Netty的特性及其如何启动,启动的相关配置说明。这一篇主要讲一下如何结合Spring搭建Netty脚手架,实现API接口服务模块。
结合Spring依赖注入的特性,在Netty的handler中获取上下文的bean,再通过uri找到匹配的方法。
maven pom配置
主要引入了Netty、Spring的依赖, 及其日志框架logback,很简单。
封装请求体
@Data
public class ApiReq {
/**
* HTTP protocol
*/
private String protocol;
/**
* ip
*/
private String remoteIp;
/**
* request type
*/
private String method;
/**
* request uri
*/
private String uri;
/**
* request headers
*/
private Map<String, String> headers;
/**
* request data
*/
private Map<String, String> data = Maps.newHashMap();
private String controllerName;
private String invokeMethodName;
public String getParam(String key) {
return data.get(key);
}
}
封装返回体
@Data
@Slf4j
public class ApiResp implements Serializable {
private static final long serialVersionUID = -8826517176378050058L;
private int result = 0;
private String msg;
private String data;
public ApiResp() {}
public static ApiResp create(String data){
ApiResp ret = new ApiResp();
ret.setMsg("success");
ret.setData(data);
return ret;
}
}
定义Controller的基类,用于后续反射调用派生类方法
public class ApiController {
public ApiResp invoke(String invokeMethodName, ApiReq req) {
try {
Method method = getClass().getMethod(invokeMethodName, req.getClass());
ApiResp result = (ApiResp) method.invoke(this, req);
return result;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
定义一个功能类
用户功能类,这里只列出一个方法,返回简单的文字描述信息。
public class UserController extends ApiController {
public ApiResp get(ApiReq req) {
return ApiResp.create("my name is " + req.getParam("name"));
}
}
实现一个Netty 启动类
回顾一下上文讲到的启动类,其实启动类对外透明化,只要很简单的几行代码,就帮我们实现了很多配置,甚至有更多的透明化配置等着我们去使用。
@Slf4j
public class HttpServer {
private AbstractControllerAdapter controllerAdapter;
public HttpServer(AbstractControllerAdapter controllerAdapter) {
this.controllerAdapter = controllerAdapter;
}
static EventLoopGroup boss = new NioEventLoopGroup();
static EventLoopGroup worker = new NioEventLoopGroup();
public void start(int port) {
log.info("############# start server at port: {}... #############", port);
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap
// 绑定两个组
.group(boss, worker)
// 创建NioServerSocketChannel实例
.channel(NioServerSocketChannel.class)
// 添加处理器Handler
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
// 为通道Channel进行初始化配置
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new HttpRequestDecoder(),
new HttpResponseEncoder(),
new HttpServerHandler(controllerAdapter)
);
}
})
// 服务于boss线程(accept connect)
.option(ChannelOption.SO_BACKLOG, 1024)
// 设置关闭tcp的Nagle算法(尽可能发送大块数据,避免网络中充斥着许多小数据块),要求高实时性
.childOption(ChannelOption.TCP_NODELAY, true)
// 设置启用心跳保活机制
.childOption(ChannelOption.SO_KEEPALIVE, true);
bootstrap.bind(port).sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
其中在初始化配置中,可以看到主要配置了三个
HttpRequestDecoder:将字节解码为HttpRequest、HttpContent和LastHttpContent消息;
HttpResponseEncoder:将HttpResponse、HttpContent和LastHttpContent消息编码为字节;
HttpServerHandler:服务中转站,下面会提到。
服务中转站
中转,即是一个协同者的角色,提供一个handler,拦截请求以执行入站和出战事件。
ChannelHandler是Netty中处理器的抽象,Netty对其提供了很多种可以开箱即用的实现,包括运用于各种协议(如HTTP SSL)的ChannelHandler,在内部也使用了事件和Future。
在这里我们用了其中的一个实现ChannelInboundHandlerAdapter来拦截和处理事件。其中涉及到Netty中对HTTP的执行流程的一个封装。
以上是一次完整的Http请求,HttpRequest是请求的第一部分,包含Http头部信息;HttpContent是请求中存放数据的块体,不止一个;LastHttpContent标志请求的结束,也包含数据,也可能包含尾随的Http头部信息。
按照这种顺序,接下来实现通过一次完整的Http请求获取数据
@Slf4j
public class HttpServerHandler extends ChannelInboundHandlerAdapter {
private AbstractControllerAdapter adapter;
private ApiReq req;
public HttpServerHandler(AbstractControllerAdapter adapter) {
this.adapter = adapter;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
long startTime = System.currentTimeMillis();
try {
if(msg instanceof HttpRequest) {
HttpRequest request = (HttpRequest) msg;
req = ApiReq.decode(request);
} else if(msg instanceof LastHttpContent) {
String controllerName = req.getControllerName();
HttpContent content = (HttpContent) msg;
Map<String, String> data = req.parseData(content);
req.setData(data);
FullHttpResponse response = ApiResp.response(adapter, req);
ctx.write(response);
}
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
super.channelReadComplete(ctx);
try {
ctx.channel().flush();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
到这里,基于Spring+Netty实现的API框架已成功搭建好了哈。
具体源码请查看github:
https://github.com/qJerry/Netty-Analyze-Demo/tree/master/Chapter1-2
Ending......
阿黑在下一章节将结合SpringBoot搭建Netty脚手架...!