1、要实现的功能
书接上回,本回书我们要完成开放接口平台核心引擎的多Handler支持机制。
如图1所示。
图1 开放接口服务器端架构
2、Filter还是装饰模式
装饰者模式貌似是一个实现的候选,类似Java的I/O实现。
多“装饰”一层,就获得了新的功能,原来的功能还在。
对我现在的应用场景来说,这种实现方式过于复杂了。
相对而言,Filter更简洁。
当前的应用场景对性能是有极高要求的,不适合使用哪怕稍微复杂的模式。
3、Handler接口定义
我的Handler接口定义如下。
public interface Handler {
public void inWay(HttpServletRequest request,HttpServletResponse response);
public void outWay(HttpServletRequest request,HttpServletResponse response);
}
更有节操的童鞋会自己定义Request、Response0,甚至Context对象。以便脱离Web容器的限制,进一步实现自己的底层通信协议。
我这里先偷个懒,等有时间了慢慢来。
Handler接口中,inWay方法对应图1左侧的向下箭头,outWay对应右侧的向上箭头。
这样,在同一个Handler定义进、出的逻辑。
对于实现序列化功能的Handler,inWay中实现反序列化,outWay中实现序列化。
对于实现加密功能的Handler,inWay中实现解密,outWay中实现加密。
对于实现压缩功能的Handler,inWay中实现解压缩的逻辑,outWay中实现压缩的逻辑。
这样,当不需要某个Handler的时候,直接去掉就好了。
当然,outWay中可以do nothing。
另外,非常重要的,Handler实现类不可以有自己的属性。Handler实例不能有“状态”。
我们需要Handler是线程安全的。
3、可配置
多个Handler是可配置的,每个Handler链可以服务于一个或多个接口。
在Handler无状态、线程安全的基础上,我们可以采用在每个JVM中Handler单例的方式,避免频繁创建、回收Handler对象的损失。
配置信息可以保存在properties文件中,可以保存在xml中,可以保存在数据中,范例如下:
openapi.handler.keys=surface,encrypt,auth
openapi.handler.surface=cn.hailong.common.openapi.handler.SurfaceHandler
openapi.handler.encrypt=cn.hailong.common.openapi.handler.EncryptHandler
openapi.handler.auth=cn.hailong.common.openapi.handler.AuthHandler
配置系统中可能用到的所有Handler,并在系统启动时加载。
在上面的配置中,配置了每个Handler的类名,加载的时候,可以根据类名创建类的实例。
给每个Handler起了一个短名称,便于在配置Handler链的时候引用。
对应的加载代码为:
public class HandlerManager {
/**
* 保存系统中的所有Handler,key为handler短名,value为Handler实例。
*/
private static Map<String, Handler> handlersMap = new ConcurrentHashMap<String, Handler>();
static{
reloadHandlers();
}
public static synchronized void reloadHandlers(){
handlersMap.clear();
logger.info("Open Api Handlers load start ... ");
long begin = System.currentTimeMillis();
Properties props = ConfigManager.getProperties("openapi");
String handlerKeys = props.getProperty("openapi.handler.keys");
logger.debug("loading handlers : "+handlerKeys);
Handler handler = null;
if(!StringUtils.isEmpty(handlerKeys)){
String[] handlerKeyArray = handlerKeys.split(",");
if(handlerKeyArray!=null && handlerKeyArray.length>0){
for (String handlerKey : handlerKeyArray) {
String propertiesKey = "openapi.handler."+handlerKey;
String handlerClassName = props.getProperty(propertiesKey);
if(StringUtils.isEmpty(handlerClassName)){
continue;
}
handlerClassName = handlerClassName.trim();
Class<?> clz = Class.forName(className);
handler = BeanUtil.newInstance(Handler.class,clz);
if(handler!=null){
handlersMap.put(handlerKey,handler);
logger.debug(String.format("handler[%s] loaded : %s ", handlerKey,handler));
}
}
}
}
logger.info("Open Api Handlers load end , time costs "+(System.currentTimeMillis()-begin));
}
/**
*
*/
public static Hanlder get(String shortName){
return handlerMap.get(shortName);
}
}
修改了Handler的配置,需要重新加载的时候,也无需重启服务器(在生产环境,这非常重要),再次调用HandlerManager.reloadHandlers()即可。
接下来是Handler链的配置,配置范例如下:
openapi.handler.chain.keys=full,idle
openapi.handler.chain.full=surface,encrypt,auth,traffic,config,validate
openapi.handler.chain.idle=idle,idle,idle,idle,idle
加载逻辑与HandlerManager中代码逻辑类似。
4、Handler执行过程
Handler的执行过程如图1所示。
public class HandlerChain {
protected List<Handler> handlersList = null;
protected List<Handler> handlersReversedList = null;
protected Iterator<Handler> inIterator = null;
protected Iterator<Handler> outIterator = null;
public HandlerChain(List<Handler> handlers) {
setHandlers(handlers);
reset();
}
protected void setHandlers(List<Handler> handlers) {
if (handlers == null) {
return;
}
// 正向
this.handlersList = handlers;
// 反向
if (handlersReversedList == null) {
handlersReversedList = new ArrayList<Handler>();
} else {
handlersReversedList.clear();
}
for (int idx = handlers.size() - 1; idx > -1; --idx) {
handlersReversedList.add(handlers.get(idx));
}
}
public void reset() {
if (handlersList != null) {
inIterator = handlersList.iterator();
}
if (handlersReversedList != null) {
outIterator = handlersReversedList.iterator();
}
}
public void inWay(HttpServletRequest request, HttpServletResponse response) {
Handler nextHandler = null;
if (inIterator != null && inIterator.hasNext()) {
nextHandler = inIterator.next();
}
if (nextHandler != null) {
nextHandler.inWay(request, response);
this.inWay(request, response);//递归调用
} else {
logger.debug(String.format("In End Time:%s.",(System.currentTimeMillis() - this.time)));
}
}
public void outWay(HttpServletRequest request, HttpServletResponse response) {
Handler nextHandler = null;
if (outIterator != null && outIterator.hasNext()) {
nextHandler = outIterator.next();
}
if (nextHandler != null) {
nextHandler.outWay(request, response);
this.outWay(request, response);
} else {
logger.debug(String.format("Out End Time:%s.",(System.currentTimeMillis() - this.time)));
}
}
}
HandlerChain 是有状态的,对每个请求创建一个实例。
调用 handlerChainInstance.inWay(req,resp)则执行了相应Handler链的所有inWay方法。
调用 handlerChainInstance.outWay(req,resp)则执行了相应Handler链的所有outWay方法。
5、消息格式参考实现
对接口的调用类似对方法的调用,传入参数包括:接口名称、参数值,传出参数可能是返回值,可能是异常消息。
定义如下接口。
interface RpcMessage{
Object getMeta();
/**
* @return 调用接口的名称。
*/
String getMethod();
/**
* @return 传入的参数值。
*/
List<Object> getParams();
/**
* @return 返回值。
*/
Object getResult();
/**
* @return 异常信息。
*/
Object getError();
}
Meta中包含可能的调用者信息,授权信息等。
对于这样的数据结构,json-rpc(http://json-rpc.org/wiki/specification)是一个简单易用的序列化方案。
虽然解析效率不高,但json足够简单,作为参考实现是个好选择。在生产环境中,需要有更成熟的考虑。
采用json-rpc作为持久化方案的情况下。
请求信息可能如下所示:
{meta:{token:‘765959559266‘},method:‘getUserInfo‘,params:[‘32899688‘]}
返回信息可能如下所示:
{result:{name:‘刘海龙‘,addr:‘人民大学北路‘}}
或
{error:{code:‘AUTH_ERR‘,msg:‘Token过期‘}}
对于每次请求应该创建一个 RpcMessage 的实例,可以保存在 Request 中,或者 ThreadLocal 中。
6、异常处理
现在需要考虑一个稍复杂的问题。
观察图1,考虑各个环节抛出异常的时候应该如何应对。考虑授权失败,或超出流量控制时应该如何应对。
首先明确一个问题,客户端是按照outWay的配置定制的。
outWay中如果有序列化和加密,客户端就会解密和反序列化。
生产环境采用的序列化方案一般是二进制的,开发环境可能采用JSON或XML。
加解密一般会针对不同用户采用不同的Key。
返回的消息如果没有经过特定格式的序列化或者加密,客户端将无法读取消息。
所以,我们得到的第一个结论是:异常消息也要报所有的Handler的outWay方法走一遍。
接下来,对图1流程中的每个环节逐个考虑。
6.1、inWay过程中的异常
通过如下代码可以捕捉到inWay过程中的异常,并保存到 rpcMessage 实例中。
try{
handlerChainInstance.inWay(req,resp)
}catch(Throwable e){
rpcMessage.addError("PRE_INVOKE_ERR",e.getMessage());
}
接下来,如果inWay中出现了异常,则跳过调用 业务逻辑 对象的代码。直接执行 outWay ,在outWay执行过程中,根据配置,该怎么序列化怎么序列化,该怎么加密怎么加密。
有一种情况是这样的,假如加密需要明确客户身份,才知道用哪个Key,但请求中未包含用户身份信息或者用户身份信息无效,这可怎么办?
为了解决这个问题,按如下两点做:
第一,用户身份标识不要在消息体中传递。否则识别不了身份,用什么Key解密都不知道,读不出来。
第二,身份无效的错误消息,定义特殊代码,明文传。客户端在执行所有Handler之前,先判断是不是这个错误。
6.2、调用业务逻辑过程中发生的异常
这个最简单,捕捉住,放到 rpcMessage 就可以了。
然后该怎么走outWay就怎么走。
6.3、outWay过程中的异常
这个最难处理。
应在开发过程中极力排除,并避免。
万一发生了,比如在加密、压缩过程中异常了,这时候也只能返回预定的消息,告诉调用方:“服务器出错了,客官请联系店小二”。
预定义的消息,根据 Handler链的配置自动生成,代码如下所示。
private static byte[] outWayErrorMessage = null;
outWayErrorMessage = buildResponseErrorMessage(handerList,"POST_ERR","服务器出错了,客官请联系店小二");