全部产品
阿里云办公

iOS端WEEX + HTTPDNS 最佳实践

更新时间:2017-11-24 12:21:30

WEEX + HTTPDNS iOS 解决方案

由于WebView并未暴露处设置DNS的接口,因而在WebView场景下使用HTTPDNS存在很多无法限制,但如果接入WEEX,则可以较好地植入HTTPDNS,本文主要介绍在WEEX场景下接入HTTPDNS的方案细节。

WEEX运行时环境下,所有的逻辑最终都会转换到Native Runtime中执行,网络请求也不例外。同时WEEX也提供了自定义相应实现的接口,通过重写网络请求适配器,我们可以较为简单地接入HTTPDNS。在WEEX运行环境中,主要有两种网络请求:

  • 通过Stream进行的网络请求
  • <image>标签指定的加载图片的网络请求

Stream网络请求 + HTTPDNS

新版本Weex SDK 实现

下面以 weex iOS 0.17.0 版本为例:

Stream 网络请求在 iOS 端最终会通过 WXResourceRequestHandlerDefaultImpl 完成,同时 WEEX 也提供了相应的接口自定义网络请求适配器。具体的逻辑如下:

第一步:创建自定义网络请求适配器,实现 WXResourceRequestHandlerHttpDnsImpl 接口

  1. #import <WeexSDK/WeexSDK.h>
  2. #import "WXResourceRequestHandlerDefaultImpl.h"
  3. @interface WXResourceRequestHandlerHttpDnsImpl : WXResourceRequestHandlerDefaultImpl<WXResourceRequestHandler,NSURLSessionDelegate>
  4. @end

第二步:在WEEX初始化时注册自定义网络适配器,替换默认适配器:

  1. [WXSDKEngine registerHandler:[WXResourceRequestHandlerHttpDnsImpl new] withProtocol:@protocol(WXResourceRequestHandler)];

下面以 weex iOS 0.7.0 版本为例:

Stream网络请求在 iOS 端最终会通过WXNetworkDefaultImpl完成,同时WEEX也提供了相应的接口自定义网络请求适配器。具体的逻辑如下:

第一步:创建自定义网络请求适配器,实现 WXNetworkHttpDnsImpl 接口

  1. #import <WeexSDK/WeexSDK.h>
  2. #import "WXNetworkDefaultImpl.h"
  3. @interface WXNetworkHttpDnsImpl : NSObject<WXNetworkProtocol, WXModuleProtocol , NSURLSessionDelegate>
  4. @end

第二步:在WEEX初始化时注册自定义网络适配器,替换默认适配器:

  1. [WXSDKEngine registerHandler:[WXNetworkHttpDnsImpl new] withProtocol:@protocol(WXNetworkProtocol)];

之后的网络请求都会通过WXNetworkHttpDnsImpl实现,所以只需要在WXNetworkHttpDnsImpl中植入 HTTPDNS 逻辑即可,具体逻辑可以参考如下代码:

  1. #import "WXResourceRequestHandlerHttpDnsImpl.h"
  2. #import "WXThreadSafeMutableDictionary.h"
  3. #import "WXAppConfiguration.h"
  4. #import <AlicloudHttpDNS/AlicloudHttpDNS.h>
  5. @interface WXResourceRequestHandlerHttpDnsImpl () <NSURLSessionDataDelegate>
  6. @property (nonatomic, strong) NSMutableURLRequest *request;
  7. @end
  8. @implementation WXResourceRequestHandlerHttpDnsImpl {
  9. NSURLSession *_session;
  10. WXThreadSafeMutableDictionary<NSURLSessionDataTask *, id<WXResourceRequestDelegate>> *_delegates;
  11. }
  12. #pragma mark - WXResourceRequestHandler
  13. - (void)sendRequest:(WXResourceRequest *)theRequest withDelegate:(id<WXResourceRequestDelegate>)delegate
  14. {
  15. self.request = [theRequest mutableCopy];
  16. NSString *originalUrl = [theRequest.URL absoluteString];
  17. NSString *originalHost = theRequest.URL.host;
  18. // 初始化httpdns实例
  19. HttpDnsService *httpdns = [HttpDnsService sharedInstance];
  20. NSString *ip = [httpdns getIpByHostAsync:theRequest.URL.host];
  21. if (ip) {
  22. // 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
  23. NSLog(@"Get IP(%@) for host(%@) from HTTPDNS Successfully!", ip, originalHost);
  24. NSRange hostFirstRange = [originalUrl rangeOfString:originalHost];
  25. if (NSNotFound != hostFirstRange.location) {
  26. NSString *newUrl = [originalUrl stringByReplacingCharactersInRange:hostFirstRange withString:ip];
  27. NSLog(@"New URL: %@", newUrl);
  28. self.request.URL = [NSURL URLWithString:newUrl];
  29. [self.request setValue:originalHost forHTTPHeaderField:@"host"];
  30. }
  31. }
  32. if (!_session) {
  33. NSURLSessionConfiguration *urlSessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
  34. if ([WXAppConfiguration customizeProtocolClasses].count > 0) {
  35. NSArray *defaultProtocols = urlSessionConfig.protocolClasses;
  36. urlSessionConfig.protocolClasses = [[WXAppConfiguration customizeProtocolClasses] arrayByAddingObjectsFromArray:defaultProtocols];
  37. }
  38. _session = [NSURLSession sessionWithConfiguration:urlSessionConfig
  39. delegate:self
  40. delegateQueue:[NSOperationQueue mainQueue]];
  41. _delegates = [WXThreadSafeMutableDictionary new];
  42. }
  43. NSURLSessionDataTask *task = [_session dataTaskWithRequest:theRequest];
  44. theRequest.taskIdentifier = task;
  45. [_delegates setObject:delegate forKey:task];
  46. [task resume];
  47. }
  48. - (void)cancelRequest:(WXResourceRequest *)request
  49. {
  50. if ([request.taskIdentifier isKindOfClass:[NSURLSessionTask class]]) {
  51. NSURLSessionTask *task = (NSURLSessionTask *)request.taskIdentifier;
  52. [task cancel];
  53. [_delegates removeObjectForKey:task];
  54. }
  55. }
  56. #pragma mark - NSURLSessionTaskDelegate & NSURLSessionDataDelegate
  57. - (void)URLSession:(NSURLSession *)session
  58. task:(NSURLSessionTask *)task
  59. didSendBodyData:(int64_t)bytesSent
  60. totalBytesSent:(int64_t)totalBytesSent
  61. totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
  62. {
  63. id<WXResourceRequestDelegate> delegate = [_delegates objectForKey:task];
  64. [delegate request:(WXResourceRequest *)task.originalRequest didSendData:bytesSent totalBytesToBeSent:totalBytesExpectedToSend];
  65. }
  66. - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)task
  67. didReceiveResponse:(NSURLResponse *)response
  68. completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
  69. {
  70. id<WXResourceRequestDelegate> delegate = [_delegates objectForKey:task];
  71. [delegate request:(WXResourceRequest *)task.originalRequest didReceiveResponse:(WXResourceResponse *)response];
  72. completionHandler(NSURLSessionResponseAllow);
  73. }
  74. - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)task didReceiveData:(NSData *)data
  75. {
  76. id<WXResourceRequestDelegate> delegate = [_delegates objectForKey:task];
  77. [delegate request:(WXResourceRequest *)task.originalRequest didReceiveData:data];
  78. }
  79. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
  80. {
  81. id<WXResourceRequestDelegate> delegate = [_delegates objectForKey:task];
  82. if (error) {
  83. [delegate request:(WXResourceRequest *)task.originalRequest didFailWithError:error];
  84. }else {
  85. [delegate requestDidFinishLoading:(WXResourceRequest *)task.originalRequest];
  86. }
  87. [_delegates removeObjectForKey:task];
  88. }
  89. #ifdef __IPHONE_10_0
  90. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
  91. {
  92. id<WXResourceRequestDelegate> delegate = [_delegates objectForKey:task];
  93. [delegate request:(WXResourceRequest *)task.originalRequest didFinishCollectingMetrics:metrics];
  94. }
  95. #endif
  96. - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
  97. forDomain:(NSString *)domain {
  98. /*
  99. * 创建证书校验策略
  100. */
  101. NSMutableArray *policies = [NSMutableArray array];
  102. if (domain) {
  103. [policies addObject:(__bridge_transfer id) SecPolicyCreateSSL(true, (__bridge CFStringRef) domain)];
  104. } else {
  105. [policies addObject:(__bridge_transfer id) SecPolicyCreateBasicX509()];
  106. }
  107. /*
  108. * 绑定校验策略到服务端的证书上
  109. */
  110. SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef) policies);
  111. /*
  112. * 评估当前serverTrust是否可信任,
  113. * 官方建议在result = kSecTrustResultUnspecified 或 kSecTrustResultProceed
  114. * 的情况下serverTrust可以被验证通过,https://developer.apple.com/library/ios/technotes/tn2232/_index.html
  115. * 关于SecTrustResultType的详细信息请参考SecTrust.h
  116. */
  117. SecTrustResultType result;
  118. SecTrustEvaluate(serverTrust, &result);
  119. return (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
  120. }
  121. #pragma mark - NSURLSessionTaskDelegate
  122. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *_Nullable))completionHandler {
  123. if (!challenge) {
  124. return;
  125. }
  126. NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
  127. NSURLCredential *credential = nil;
  128. /*
  129. * 获取原始域名信息。
  130. */
  131. NSString *host = [[self.request allHTTPHeaderFields] objectForKey:@"host"];
  132. if (!host) {
  133. host = self.request.URL.host;
  134. }
  135. if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
  136. if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:host]) {
  137. disposition = NSURLSessionAuthChallengeUseCredential;
  138. credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
  139. } else {
  140. disposition = NSURLSessionAuthChallengePerformDefaultHandling;
  141. }
  142. } else {
  143. disposition = NSURLSessionAuthChallengePerformDefaultHandling;
  144. }
  145. // 对于其他的challenges直接使用默认的验证方案
  146. completionHandler(disposition, credential);
  147. }
  148. @end
旧版本Weex SDK 实现

0.7.0为例子:

网络请求都会通过WXNetworkHttpDnsImpl实现,所以只需要在WXNetworkHttpDnsImpl中植入 HTTPDNS 逻辑即可,具体逻辑可以参考如下代码:

  1. #import "WXNetworkDefaultImpl.h"
  2. @interface WXNetworkCallbackInfo : NSObject
  3. @property (nonatomic, copy) void(^sendDataCallback)(int64_t, int64_t);
  4. @property (nonatomic, copy) void(^responseCallback)(NSURLResponse *);
  5. @property (nonatomic, copy) void(^receiveDataCallback)(NSData *);
  6. @property (nonatomic, strong) NSMutableData *data;
  7. @property (nonatomic, copy) void(^compeletionCallback)(NSData *, NSError *);
  8. @end
  9. @implementation WXNetworkCallbackInfo
  10. @end
  11. @implementation WXNetworkDefaultImpl
  12. {
  13. NSMutableDictionary *_callbacks;
  14. NSURLSession *_session;
  15. }
  16. - (id)sendRequest:(NSURLRequest *)request withSendingData:(void (^)(int64_t, int64_t))sendDataCallback
  17. withResponse:(void (^)(NSURLResponse *))responseCallback
  18. withReceiveData:(void (^)(NSData *))receiveDataCallback
  19. withCompeletion:(void (^)(NSData *, NSError *))compeletionCallback
  20. {
  21. WXNetworkCallbackInfo *info = [WXNetworkCallbackInfo new];
  22. info.sendDataCallback = sendDataCallback;
  23. info.responseCallback = responseCallback;
  24. info.receiveDataCallback = receiveDataCallback;
  25. info.compeletionCallback = compeletionCallback;
  26. if (!_session) {
  27. _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
  28. delegate:self
  29. delegateQueue:[NSOperationQueue mainQueue]];
  30. }
  31. NSURLSessionDataTask *task = [_session dataTaskWithRequest:request];
  32. if (!_callbacks) {
  33. _callbacks = [NSMutableDictionary dictionary];
  34. }
  35. [_callbacks setObject:info forKey:task];
  36. [task resume];
  37. return task;
  38. }
  39. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
  40. didSendBodyData:(int64_t)bytesSent
  41. totalBytesSent:(int64_t)totalBytesSent
  42. totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
  43. {
  44. WXNetworkCallbackInfo *info = [_callbacks objectForKey:task];
  45. if (info.sendDataCallback) {
  46. info.sendDataCallback(totalBytesSent, totalBytesExpectedToSend);
  47. }
  48. }
  49. - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)task
  50. didReceiveResponse:(NSURLResponse *)response
  51. completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
  52. {
  53. WXNetworkCallbackInfo *info = [_callbacks objectForKey:task];
  54. if (info.responseCallback) {
  55. info.responseCallback(response);
  56. }
  57. completionHandler(NSURLSessionResponseAllow);
  58. }
  59. - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)task didReceiveData:(NSData *)data
  60. {
  61. WXNetworkCallbackInfo *info = [_callbacks objectForKey:task];
  62. if (info.receiveDataCallback) {
  63. info.receiveDataCallback(data);
  64. }
  65. NSMutableData *mutableData = info.data;
  66. if (!mutableData) {
  67. mutableData = [NSMutableData new];
  68. info.data = mutableData;
  69. }
  70. [mutableData appendData:data];
  71. }
  72. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
  73. {
  74. WXNetworkCallbackInfo *info = [_callbacks objectForKey:task];
  75. if (info.compeletionCallback) {
  76. info.compeletionCallback(info.data, error);
  77. }
  78. [_callbacks removeObjectForKey:task];
  79. }
  80. @end

<image> 网络请求 + HTTPDNS

WEEX并没有提供默认的图片适配器实现,所以用户必须自行实现才能完成图片请求逻辑,具体步骤分为以下几步:

第一步:自定义图片请求适配器,实现IWXImgLoaderAdapter接口

  1. #import "WXImgLoaderProtocol.h"
  2. @interface WXImgLoaderDefaultImpl : NSObject<WXImgLoaderProtocol, WXModuleProtocol>
  3. @end

第二步:在WEEX初始化时注册该图片适配器:

  1. [WXSDKEngine registerHandler:[WXImgLoaderDefaultImpl new] withProtocol:@protocol(WXImgLoaderProtocol)];

所以同WXNetworkHttpDnsImpl一样,我们只需在WXNetworkHttpDnsImpl植入HTTPDNS逻辑即可。