如何做一个简单的开放接口(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<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","服务器出错了,客官请联系店小二");
时间: 2024-10-14 08:29:12

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

如何做一个简单的开放接口(4)-常见Handler的参考实现

1.概述 核心引擎搞定了,接下来的主要工作就是逐个开发 Handler 了. 常用的Handler包括授权(AuthHandler).流量控制(TrafficControlHandler).加解密(EncryptHandler).安全(SecurityHandler).压缩(ZipHandler).序列化(KryoHandler)等. 其他外围功能还包括对调用方的管理功能,开放接口介绍网站等,不再冗述. 2.常用Handler 2.1.授权 实现一个达到实用级别的授权实现需要另开一个专题来讲,这

如何做一个简单的开放接口(1)-功能设计

1.缘起 最初,系统系统间都是孤立的.业务是贯穿的,系统间也必然需要交互数据. 实现数据交互的方式有好多种,可以通过ftp交互Excel文件,可以通过互相读写的中间库,可以通过Web Services. 系统间可能是点对点交互,可能是一对多广播,可能是多对一汇总,可能是多对多协同. 在复杂IT场景中,多信息系统各司其职,协作完成工作.交互数据的事情怎样做呢? 数据交互有两个核心问题要解决:一是协议,二是数据格式.这两个都需要通信双方协商. 如果是企业内部的各信息系统,可以搭建统一的数据交互平台解

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

1.要实现的功能 书接上回,本回书解决核心引擎的第二个问题:数据映射和数据校验. 我们把这个部分叫做数据转换模块. 2.输入数据的格式 输入数据的结构.属性名等,是接口发布方确定的. 出于安全.效率.调用方影响等方面的考虑,可能和自身系统中的结构和属性名不一致. 输入数据的格式可能有三种: 反序列化后得到的Java对象. JSON格式. XML格式. 我们将对输入的数据进行校验,转换成自身系统的数据格式. 3.配置 配置文件是数据转换模块的核心. 对于我方来说,数据的结构和格式是相对稳定的. 举

【Bugly干货分享】一起用 HTML5 Canvas 做一个简单又骚气的粒子引擎

Bugly 技术干货系列内容主要涉及移动开发方向,是由Bugly邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 前言 好吧,说是“粒子引擎”还是大言不惭而标题党了,离真正的粒子引擎还有点远.废话少说,先看[demo],扫描后点击屏幕有惊喜哦… 本文将教会你做一个简单的canvas粒子制造器(下称引擎). 世界观 这个简单的引擎里需要有三种元素:世界(World).发射器(Launcher).粒子(Grain).总得来说就是:发射器存在于世界之中,

使用React并做一个简单的to-do-list

1. 前言 说到React,我从一年之前就开始试着了解并且看了相关的入门教程,而且还买过一本<React:引领未来的用户界面开发框架 >拜读.React的轻量组件化的思想及其visual-dom的这种技术创新,也算是早就有了初步了解.一来没有学的太深入,二来后来在工作中和业余项目中都没有用到,因此慢慢的就更加生疏了. 近期,因为我想把自己的开源项目wangEditor能放在React.angular和vuejs中使用.先从react开始,顺手自己也重试一下React的基础知识,顺便再做一个小d

C#做一个简单的进行串口通信的上位机

C#做一个简单的进行串口通信的上位机 1.上位机与下位机 上位机相当于一个软件系统,可以用于接收数据.控制数据.即可以对接收到的数据直接发送操控命令来操作数据.上位机可以接收下位机的信号.下位机是一个控制器,是直接控制设备获取设备状况的计算机.上位机发出的命令首先给下位机,下位机再根据此命令解释成相应时序信号直接控制相应设备.下位机不时读取设备状态数据(一般为模拟量),转换成数字信号反馈给上位机.上位机不可以单独使用,而下位机可以单独使用. 2.串口通信 串口相当于硬件类型的接口.比如无线传感节

使用Multiplayer Networking做一个简单的多人游戏例子-2/3(Unity3D开发之二十六)

猴子原创,欢迎转载.转载请注明: 转载自Cocos2Der-CSDN,谢谢! 原文地址: http://blog.csdn.net/cocos2der/article/details/51007512 使用Multiplayer Networking做一个简单的多人游戏例子-1/3 使用Multiplayer Networking做一个简单的多人游戏例子-2/3 使用Multiplayer Networking做一个简单的多人游戏例子-3/3 7. 在网络中控制Player移动 上一篇中,玩家操

[3] 用D3.js做一个简单的图表吧!

本人的个人博客为: www.ourd3js.com csdn博客为: blog.csdn.net/lzhlzz 转载请注明出处,谢谢. 前面说了几节,都是对文字进行处理,这一节中将用 D3.js 做一个简单的柱形图. 做柱形图有很多种方法,比如用 HTML 的 div 标签,或用 svg . 推荐用 SVG 来做各种图形.SVG 意为可缩放矢量图形(Scalable Vector Graphics),SVG 使用 XML 格式定义图像,不清楚什么是SVG的朋友请先在 w3cschools 学习下

用EF DataBase First做一个简单的MVC3报名页面

使用EF DataBase First做一个简单的MVC3报名网站 ORM(Object Relational Mapping)是面向对象语言中的一种数据访问技术,在ASP.NET中,可以通过ADO.NET Entity Framework技术来简化数据访问.在EF里,有Code First,Model First和DataBase First三种方法来实现. 百度百科关于ORM的介绍: http://baike.baidu.com/view/197951.htm?fr=aladdin 1.就像