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

Posted mPaaS

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mPaaS-RPC 拦截器各种场景下的使用指南相关的知识,希望对你有一定的参考价值。

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的定制处理。

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



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; }


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


mPaaS 发起了一个读者讨论 【参与讨论随机抽取一份限量礼品】针对客户端RPC的场景诉求,说说你的解决方案吧



END -



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


关注公众号,回复“ 前端 ”,查看内推职位详情
点击下方「 阅读原文 」了解“MGS 移动网关”

以上是关于mPaaS-RPC 拦截器各种场景下的使用指南的主要内容,如果未能解决你的问题,请参考以下文章

Android智能手机中各种音频场景下的audio data path

flink standalone 部署模式且不能使用 hdfs 场景下的各种问题及其应对方案

Android 逆向函数拦截 ( ARM 架构下的插桩拦截 | 完整代码示例 )

高并发场景下的 HttpClient 优化,QPS 大大提升!

微服务下的登录实现及相关问题解决

Mybatis自定义拦截器与插件开发