如何做一个简单的开放接口(2)-核心引擎(上)

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 handlersMap = new ConcurrentHashMap();

    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 handlersList = null;
    protected List handlersReversedList = null;

    protected Iterator inIterator = null;
    protected Iterator outIterator = null;

    public HandlerChain(List handlers) {
        setHandlers(handlers);
        reset();
    }

    protected void setHandlers(List handlers) {
        if (handlers == null) {
            return;
        }
        // 正向
        this.handlersList = handlers;
        // 反向
        if (handlersReversedList == null) {
            handlersReversedList = new ArrayList();
        } 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 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","服务器出错了,客官请联系店小二");

你可能感兴趣的:(碎碎念)