mPaaS-RPC 拦截器各种场景下的使用指南

本文涉及的产品
mPaaS订阅基础套餐,标准版 3个月
简介: RPC拦截器机制在preHandle、postHandle、exceptionHandle以及H5等场景中的应用

封面图0929.jpg

mPaaS 移动网关服务(Mobile Gateway Service,简称 MGS)作为 mPaas 最重要的组件之一,连接了移动客户端与服务端,简化了移动端与服务端的数据协议和通讯协议,从而能够显著提升开发效率和网络通讯效率。

在我们日常运维过程中发现,很多用户在使用客户端RPC组件的时候,有很多不同场景的诉求,比如拦截请求添加业务请求标记,免登,返回结果模拟,异常处理,限流等。

本文旨在介绍通过利用RPC提供的拦截器机制,通过不同实际场景的描述,供业务参考使用。

RPC调用原理

当 App 在移动网关控制台接入后台服务后,调用RPC的示例代码如下:

RpcDemoClient client = MPRpc.getRpcProxy(RpcDemoClient.class);
// 设置请求
GetIdGetReq req = new GetIdGetReq();
req.id = "123";
req.age = 14;
req.isMale = true;
// 发起 rpc 请求
String response = client.getIdGet(req);

值得好奇的是,整个调用过程中其实我们并没有去实现 RpcDemoClient 这个接口,而是通过 MPRpc.getRpcProxy 获取了一个代理,通过代理对象完成了调用。在这里其实主要使用了 Java 动态代理的技术。

如下图所示,当调用RPC接口的时候,会通过动态代理的RpcInvocationHandler,回调其实现的invoke方法,最终在invoke内实现数据的序列化处理最后通过网络库发到服务端。

public <T> T getRpcProxy(Class<T> clazz) {
        LogCatUtil.info("RpcFactory", "clazz=[" + clazz.getName() + "]");
        return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new RpcInvocationHandler(this.mConfig, clazz, this.mRpcInvoker));
    }

在业务开发中,如果在某些情况下需要控制客户端的网络请求(拦截网络请求,禁止访问某些接口,或者限流),可以通过 RPC 拦截器实现。

RpcService rpcService = getMicroApplicationContext().findServiceByInterface(RpcService.class.getName());
        rpcService.addRpcInterceptor(OperationType.class, new CommonInterceptor());

拦截器原理

RPC目前采用了拦截器机制实现RPC的自定义处理,如下图所示,业务可以通过设置自定义RpcIntercept实现在请求前,请求异常,请求返回三个阶段对RPC的定制处理。

图片1.png

preHandle场景

1.全局添加业务自定义请求header

典型使用场景:业务添加自定义的业务全局标识或者其他统计字段。

@Override
    public boolean preHandle(Object proxy,
                             ThreadLocal<Object> retValue,
                             byte[] retRawValue,
                             Class<?> aClass,
                             Method method,
                             Object[] args,
                             Annotation annotation,
                             ThreadLocal<Map<String, Object>> threadLocal)
            throws RpcException {
        //Do something...
        RpcInvocationHandler handler = (RpcInvocationHandler) Proxy.getInvocationHandler(proxy);
        handler.getRpcInvokeContext().addRequestHeader("header", "headerCustom");
        return true;
    }

2.阻断当前请求rpc流程

典型使用场景:比如如果当前未登录,对需要登录的rpc先阻断,统一提示登录。

@Override
    public boolean preHandle(Object proxy,
                             ThreadLocal<Object> retValue,
                             byte[] retRawValue,
                             Class<?> aClass,
                             Method method,
                             Object[] args,
                             Annotation annotation,
                             ThreadLocal<Map<String, Object>> threadLocal)
            throws RpcException {
        //Do something...
        String operationType = getOperationType(aClass, method, args);
        if ("operationType1".equals(operationType)) {
            boolean isLogin = false;
            if (!isLogin) {
                Handler handler = new Handler(Looper.getMainLooper());
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(LauncherApplicationAgent.getInstance().getApplicationContext(),
                                "当前未登录,请登录", Toast.LENGTH_SHORT).show();
                    }
                });
                // 返回给上层调用登录失败的异常,上层做业务处理
                throw new RpcException(RpcException.ErrorCode.CLIENT_LOGIN_FAIL_ERROR, "login fail.");
            }
        }
        return true;
    }
     private String getOperationType(Class<?> aClass, Method method, Object[] args) {
        if (aClass == null || null == method) return "";
        OperationType operationType = method.getAnnotation(OperationType.class);
        return operationType == null ? "" : operationType.value();
    }

postHandle场景

1.拦截接口返回

典型使用场景:全局修改服务端的返回结果,比如mock服务端的数据。

@Override
    public boolean postHandle(Object proxy,
                              ThreadLocal<Object> threadLocal,
                              byte[] retRawValue,
                              Class<?> aClass,
                              Method method,
                              Object[] args,
                              Annotation annotation) throws RpcException {
        //Do something...
        // 场景:修改服务端返回的数据,比如mock数据,或者修改服务端数据
        String operationType = getOperationType(aClass, method, args);
        LoggerFactory.getTraceLogger().debug(TAG, "postHandle:" + operationType);
        if ("operationType1".equals(operationType)) {
            String value = JSON.parse(retRawValue).toString();
            LoggerFactory.getTraceLogger().debug(TAG, "postHandle 原始返回" + value);
            String mockData = "{\"img\":\"imgPath\",\"User\":{\"name\":\"我是mock的数据\",\"age\":18}}";
            Object mockObj = JSON.parseObject(mockData, method.getReturnType());
            threadLocal.set(mockObj);
            return true;
        }
        return true;
    }

    private String getOperationType(Class<?> aClass, Method method, Object[] args) {
        if (aClass == null || null == method) return "";
        OperationType operationType = method.getAnnotation(OperationType.class);
        return operationType == null ? "" : operationType.value();
    }

exceptionHandle场景

1.异常统一处理

比如登录态失效,服务端会统一返回2000的错误码,客户端可以在exceptionHandle里统一拦截进行登录态免登操作。

@Override
    public boolean exceptionHandle(Object proxy, ThreadLocal<Object> retValue, byte[] bytes, Class<?> aClass, Method method, Object[] objects,
                                   RpcException rpcException, Annotation annotation) throws RpcException {
        String operationType = getOperationType(aClass, method, objects);
        if (RpcException.ErrorCode.CLIENT_LOGIN_FAIL_ERROR == rpcException.getCode()
                && "operationType1".equals(operationType)) {
            // 1. 去免登
            hasLogin = true;
            // 2. 免登后在帮上层重发请求,免登操作对上层业务无感知
            try {
                LoggerFactory.getTraceLogger().debug(TAG, "exceptionHandle. Start resend rpc begin " + operationType);
                // 重发请求
                Object object = method.invoke(proxy, objects);
                retValue.set(object);
                LoggerFactory.getTraceLogger().debug(TAG, "exceptionHandle. Start resend rpc success");
                return false;
            } catch (Throwable e) {
                LoggerFactory.getTraceLogger().error(TAG, "resend rpc occurs illegal argument exception", e);
                throw new RpcException(RpcException.ErrorCode.CLIENT_HANDLE_ERROR, e + "");
            }
        }
        return true;
    }

H5场景

由于H5场景中使用的jsapi的rpc,需要支持在js环境里传递到native环境,所以在设计上,是统一通过operationType: alipay.client.executerpc 接口进行的转发,所以针对H5发送的RPC请求,需要做特殊判断,通过入参拿到真实的operationType接口,示例代码如下。

1.获取H5请求的接口名称和入参

var params = [{
    "_requestBody":{"userName":"", "userId":0}
}]
var operationType = 'alipay.mobile.ic.dispatch'
AlipayJSBridge.call('rpc', {
  operationType: operationType,
  requestData: params,
  headers:{}
}, function (result) {
  console.log(result);
});

如上图所示,业务通过jsapi去请求rpc,如何获取jsapi请求的rpc名称,可以参考代码如下:

@Override
    public boolean preHandle(Object o, ThreadLocal<Object> threadLocal, byte[] bytes, Class<?> aClass, Method method, Object[] objects, Annotation annotation, ThreadLocal<Map<String, Object>> threadLocal1) throws RpcException {
        String operationType = getOperationType(aClass, method, objects);
        if ("alipay.client.executerpc".equals(operationType)) {
            // H5的rpc名称
            String rpcName = (String) objects[0];
            // 入参
            String req = (String) objects[1];
            LoggerFactory.getTraceLogger().debug(TAG, "operationType:" + rpcName + " " + req);

        } else {
            // Native的rpc
        }
        LoggerFactory.getTraceLogger().debug(TAG, "operationType:" + operationType);
        return true;
    }

作者名片-荣阳.jpg

延伸阅读.png


动态-logo.gif

底部banner.png

点击此处了解“MGS 移动网关”更多资讯

访问2020阿里巴巴双11技术全观专题页,了解更多关于2020双11的技术干货内容

相关文章
|
移动开发 开发框架 小程序
蚂蚁mPaaS上线音视频通话组件,以技术创新构建场景生态
远程问诊、线上开户、车载语音通话……蚂蚁mPaaS正在“拥抱新技术,探索新未来”。
蚂蚁mPaaS上线音视频通话组件,以技术创新构建场景生态
|
8月前
|
负载均衡 Dubbo Java
Dubbo 3.x:探索阿里巴巴的开源RPC框架新技术
随着微服务架构的兴起,远程过程调用(RPC)框架成为了关键组件。Dubbo,作为阿里巴巴的开源RPC框架,已经演进到了3.x版本,带来了许多新特性和技术改进。本文将探讨Dubbo 3.x中的一些最新技术,包括服务注册与发现、负载均衡、服务治理等,并通过代码示例展示其使用方式。
436 9
|
8月前
|
JSON 负载均衡 网络协议
Rpc编程系列文章第二篇:RPC框架设计目标
Rpc编程系列文章第二篇:RPC框架设计目标
|
8月前
|
设计模式 负载均衡 网络协议
【分布式技术专题】「分布式技术架构」实践见真知,手把手教你如何实现一个属于自己的RPC框架(架构技术引导篇)
【分布式技术专题】「分布式技术架构」实践见真知,手把手教你如何实现一个属于自己的RPC框架(架构技术引导篇)
332 0
|
8月前
|
Dubbo Java 应用服务中间件
Rpc编程系列文章第三篇:Hessian RPC一个老的RPC框架
Rpc编程系列文章第三篇:Hessian RPC一个老的RPC框架
|
2月前
|
自然语言处理 负载均衡 API
gRPC 一种现代、开源、高性能的远程过程调用 (RPC) 可以在任何地方运行的框架
gRPC 是一种现代开源高性能远程过程调用(RPC)框架,支持多种编程语言,可在任何环境中运行。它通过高效的连接方式,支持负载平衡、跟踪、健康检查和身份验证,适用于微服务架构、移动设备和浏览器客户端连接后端服务等场景。gRPC 使用 Protocol Buffers 作为接口定义语言,支持四种服务方法:一元 RPC、服务器流式处理、客户端流式处理和双向流式处理。
|
5月前
|
Dubbo 网络协议 Java
RPC框架:一文带你搞懂RPC
这篇文章全面介绍了RPC(远程过程调用)的概念、原理和应用场景,解释了RPC如何工作以及为什么在分布式系统中广泛使用,并探讨了几种常用的RPC框架如Thrift、gRPC、Dubbo和Spring Cloud,同时详细阐述了RPC调用流程和实现透明化远程服务调用的关键技术,包括动态代理和消息的编码解码过程。
RPC框架:一文带你搞懂RPC
|
4月前
|
XML 负载均衡 监控
分布式-dubbo-简易版的RPC框架
分布式-dubbo-简易版的RPC框架
|
7月前
|
存储 缓存 Linux
【实战指南】嵌入式RPC框架设计实践:六大核心类构建高效RPC框架
在先前的文章基础上,本文讨论如何通过分层封装提升一个针对嵌入式Linux的RPC框架的易用性。设计包括自动服务注册、高性能通信、泛型序列化和简洁API。框架分为6个关键类:BindingHub、SharedRingBuffer、Parcel、Binder、IBinder和BindInterface。BindingHub负责服务注册,SharedRingBuffer实现高效数据传输,Parcel处理序列化,而Binder和IBinder分别用于服务端和客户端交互。BindInterface提供简单的初始化接口,简化应用集成。测试案例展示了客户端和服务端的交互,验证了RPC功能的有效性。
467 8

热门文章

最新文章

相关产品

  • 移动开发平台 mPaaS