1987WEB视界-分享互联网热门产品和行业

您现在的位置是:首页 > 域名 > 正文

域名

有赞App如何实现动态域名

1987web2022-08-20域名438

作者:杨彬&李子

部门:社交电商

一、概述

在移动开发中,网络层面的监控一直是非常有必要的,比如统计网络接口的失败率、重定向网络请求、网络Request增加公共header头、实现动态域名等等。经常会遇到App某些域名因为一些原因在某些地区DNS解析异常,因此我们需要将这些有问题的域名进行动态替换,让用户可以正常的访问接口,正常使用我们的App。

二、具体方案

动态域名其实就是网络请求的URL的Host实现动态替换的能力,我们可以从监听、拦截网络请求方面入手来达到动态域名替换的目的。有赞目前的App大都使用Weex、Flutter、H5进行跨平台开发,在技术选择上我们尽量做到统一,沉淀出一套通用能力。由于Weex网络请求采用原生桥接的方式,因此对于Weex和Native的网络请求,只需要对Native端网络请求做处理,最终采用拦截Native网络请求的方式,Flutter和H5会在后文介绍。

2.1 配置中心结合Native

有赞配置中心平台是为了满足App灵活开关配置类需求开发的统一管理平台,可以对差异的功能划分不同的组件,给运营人员和开发人员发布新配置的功能,结合长连接能力,能够达到实时获取配置效果。那我们的思路就是利用配置中心的能力,结合Native网络拦截方法实现App动态域名能力,流程如下图所示:

整个方案存在一个问题,我们可以设想一下,万一配置中心的域名DNS解析异常,我们该如何去做?

2.2 配置中心备用域名

我们从运维那边申请了配置中心的备用域名,在配置中心请求接口做了降级策略,这样就可以保证配置中心接口在极端情况下也可以正常访问,整体方案流程如下图所示:

三、Native技术方案

3.1 iOS

在iOS开发中.常用到的网络请求三方库有AFNetworking和Alamofire,它们的底层是基于苹果提供的NSURLConnection、NSURLSession网络库接口进行了封装,那么想要拦截到网络请求,就需要使用官方提供的处理URL数据的类NSURLProtocol 。

3.1.1 NSURLProtocol

An abstract class that handles the loading of protocol-specific URL data.苹果官方文档这样介绍NSURLProtocol,一个处理加载协议特定URL数据的抽象类,看起来像是一个协议,其实这是一个类,支持创建该子类来支持自定义网络请求,先看看URL Loading System架构图:

在每一个HTTP请求开始,URL会加载系统创建的NSURLProtocol对象处理对应的URL请求,根据文档我们只需要创建一个子类继承自NSURLProtocol,通过registerClass:方法注册我们自定义的网络协议类,来实现网络拦截的目的。那么,我们需要解决的问题就是使用自定义的NSURLProtocol来处理App所有的网络请求,苹果官方文档中CustomHTTPProtocol介绍了如何自定义NSURLPtotocol来实现网络拦截。回到之前的问题,我们如何使用NSURLProtocol拦截Http请求?只需要判断对于那些请求request需要处理;对于需要处理的request做出哪些处理;再将响应请求的数据传递给调用者。

这里我们将基于NSURLSession为例来说明如何进行自定义网络拦截,达到动态域名替换的目的。

3.1.2 返回需要控制的请求

在NSRULPtotocol,要知道哪些网络请求是需要被拦截,通过重写canInitWithRequest:比如我们可以拦截全部的http/https请求。

3.1.3 自定义request

每一次请求都会有一个NSURLRequest实例,上述方法会拿到所有的请求对象,我们就可以根据对应的请求选择是否处理该对象请求经过 + canInitWithRequest:方法过滤之后,我们得到了所有要处理的请求,接下来需要对请求进行一定的操作。这里对请求不做任何修改,直接返回。

3.1.4 对处理过的request进行标记

通过这两个方法,就已经能够拦截住iOS的网络请求了,但是我们需要对每个处理过的request进行标记,判断如果这个request已经处理过,那么我们就不再进行处理。

我们在 + canInitWithRequest: 中判断是否有处理过的标志,来进行拦截。

3.1.5 开始网络请求

当我们处理了这个请求后,就需要我们手动去发送这个网络请求,即重写starLoading方法。

这里我简化了代码,在这个方法里面根据配置中心下发的replaceHost域名可以对targetHost域名进行动态替换,也可以将request做一些自定的处理,比如增加统一的header头等处理。

3.1.6 停止相应的请求

取消网络请求的task,将task置为nil。

3.1.7 实现NSURLSessionTaskDelegate

然后将自定义的protocol注册到NSURLProtocol中即可这样就可以拦截UIWebView和自定义的网络请求了,如果要拦截AFNetworking、Alamofire等三方库请求,我们需要将NSURLSessionConfiguration类,用Method Swizzle将protocolClasses替换成自己定义的 CustomeURLProtocol。以上就是自定义NSURLProtocol大体流程,配合上配置中心,我们就可以实现动态域名替换,当然你还可以做以下事情:

统计网络接口的失败率重定向网络请求自定义修改request自定义返回网络请求的结果3.2 Android

3.2.1 对OKHttp插桩

Android端App基本使用OKHttp 和HttpUrlConnection/HttpsUrlConnection两种框架来进行网络请求,因此只需要对以上两种网络请求做插桩来达到网络请求拦截的效果。方案图如下:

3.2.2 插桩实现

拿到OkHttpClient之后可以设置很多属性如:

3.2.3 UrlConnection插桩

通过以下方式插桩可以拿到URLConnection的入参URL,因此也可以动态控制域名。

3.2.4 使用

a、build.gradle添加引用

b、app/build.gradle添加代码扫描配置

c、Application中主动拉取动态域名配置

d、扩展能力

四、跨平台

4.1 Flutter

目前我们使用的Flutter网络请求分为:图片下载请求和普通数据网络请求,数据网络请求我们采用插件方式,封装了Native的网络请求库,不需要做单独的处理,图片加载使用的Flutter自己的渲染引擎,下面来介绍下Flutter图片下载如何去做动态域名。

4.1.1 Flutter渲染流程图

Layer Tree:这个是dart runtime输出的一个树状数据结构,树上的每一个叶子节点,代表了一个界面元素(Button,Image等等)。

Skia:这个是谷歌的一个跨平台渲染框架,从目前 iOS 和 Anrdroid 来看,SKIA底层最终都是调用OpenGL绘制。Vulkan支持还不太好,Metal还不支持。

Shell:这里的Shell特指平台特性(Platform)的那一部分,包含IOS和Android平台相关的实现,包括EAGLContext管理、上屏的操作以及后面将会重点介绍的外接纹理实现等等。

从图中可以看出,当Runtime完成Layout输出一个 Layertree 以后,在管线中会遍历Layertree的每一个叶子节点,每一个叶子节点最终会调用Skia引擎完成界面元素的绘制,在遍历完成后,在调用 glPresentRenderBuffer(IOS)或者 glSwapBuffer(Android) 按完成上屏操作。

基于这个基本原理,Flutter在Nativ e和Flutter Engine上实现了UI的隔离,书写UI代码时不用再关心平台实现从而实现了跨平台。

4.1.2 外接纹理

上图是前文提到的LayerTree的一个简单架构图,每一个叶子节点代表了dart代码排版的一个控件,可以看到最后有一个TextureLayer节点,这个节点对应的是Flutter里的Texture控件(这里的Texture和GPU的Texture不一样,这个是Flutter的控件)。当在Flutter里创建出一个Texture控件时,代表的是在这个控件上显示的数据,需要由Native提供。以iOS端为例,TexttureLayer节点的最终绘制整体过程可以分为三步:调用external_texture copyPixelBuffer,获取CVPixelBuffer CVOpenGLESTextureCacheCreateTextureFromImage创建OpenGL的Texture 将OpenGL Texture 封装成S KImage,调用Skia的DrawImage完成绘制。

通过外接纹理的方式,实际上Flutter和Native传输的数据载体就是PixelBuffer,Native端的数据源将数据写入PixelBuffer,Flutter拿到PixelBuffer以后转成OpenGLES Texture,交由Skia绘制。

4.1.3 ShareGroup

App中使用OpenGL来渲染都会有两个线程,一个负责加载资源,一个负责渲染的方式。这两个线程会共用一个EAGLContext。Flutter在EAGLContext的处理上采用两个线程彼此通过ShareGroup来共享纹理数据。在Flutter创建的Context时,将它们的ShareGroup透出。在Native通过OpenGL渲染的模块创建Context时,在Native侧保存好这个ShareGroup ,这样当Native创建Context时,都会使用这个ShareGroup进行创建,这样就实现了Native和Flutter之间的纹理共享。

4.1.4 原生处理

总结整个方案,通过外接纹理的方式,Flutter就可以很容易绘制出大型图片加载库SDWebImage等,本质是共用了一套缓存,将图片网络加载的工作转移到了Native端,从而实现了图片URL动态域名的需求,至于网络请求,Flutter完全可以使用网络库插件,本质也是调用Native的网络库。

4.2 H5

上面介绍的Native方法对于H5请求来说并不能做到拦截网络,比如iOS基于NSURLProtocol 实现自定义拦截网络请求,并不能拦截WKWebView的网络请求,市面上也有很多方法可以拦截WKWebVIew网络请求。我们这边的方案是让前端来对域名进行动态配置,如果检测到域名访问异常,就激活配置中心,替换新的域名让商家能够正常的访问,整体的业务流程设计如下图所示:

五、总结与展望

未来将拦截网络请求的效果达到最大化,可以监控网络Request和Response;可以做到统计接口失败率;可以做到App内部统计一些接口访问量;App内所有特定请求增加公共的 header;可以返回自定义的Response等等,简单来讲就是网络数据的收发,都可以监控并做自定义操作。

举报/反馈