全部产品

iOS端HTTPDNS+Weex最佳实践

更新时间:2020-12-11 18:42

注意

当前最佳实践文档只针对结合使用时,如何使用HTTPDNS解析出的IP,关于HTTPDNS本身的解析服务,请先查看iOS SDK 开发手册

背景信息

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

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

  • 通过 Stream进行的网络请求

  • 标签指定的加载图片的网络请求

Stream网络请求+HTTPDNS

新版本Weex SDK实现

下面以 weex iOS 0.17.0 版本为例:

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

  1. 创建自定义网络请求适配器,实现 WXResourceRequestHandlerHttpDnsImpl 接口

    #import <WeexSDK/WeexSDK.h>
    #import "WXResourceRequestHandlerDefaultImpl.h"
    
    @interface WXResourceRequestHandlerHttpDnsImpl : WXResourceRequestHandlerDefaultImpl<WXResourceRequestHandler,NSURLSessionDelegate>
    
    @end
  2. WEEX初始化时注册自定义网络适配器,替换默认适配器:

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

下面以 weex iOS 0.7.0 版本为例:

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

  1. 创建自定义网络请求适配器,实现 WXNetworkHttpDnsImpl 接口

    #import <WeexSDK/WeexSDK.h>
    #import "WXNetworkDefaultImpl.h"
    
    @interface WXNetworkHttpDnsImpl : NSObject<WXNetworkProtocol, WXModuleProtocol , NSURLSessionDelegate>
    
    @end
  2. WEEX初始化时注册自定义网络适配器,替换默认适配器:

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

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

    #import "WXResourceRequestHandlerHttpDnsImpl.h"
    #import "WXThreadSafeMutableDictionary.h"
    #import "WXAppConfiguration.h"
    #import <AlicloudHttpDNS/AlicloudHttpDNS.h>
    
    @interface WXResourceRequestHandlerHttpDnsImpl () <NSURLSessionDataDelegate>
    
    @property (nonatomic, strong) NSMutableURLRequest *request;
    
    @end
    
    @implementation WXResourceRequestHandlerHttpDnsImpl {
        NSURLSession *_session;
        WXThreadSafeMutableDictionary<NSURLSessionDataTask *, id<WXResourceRequestDelegate>> *_delegates;
    }
    
    #pragma mark - WXResourceRequestHandler
    
    - (void)sendRequest:(WXResourceRequest *)theRequest withDelegate:(id<WXResourceRequestDelegate>)delegate
    {
        self.request = [theRequest mutableCopy];
        NSString *originalUrl = [theRequest.URL absoluteString];
        NSString *originalHost = theRequest.URL.host;
    
        // 初始化httpdns实例
        HttpDnsService *httpdns = [HttpDnsService sharedInstance];
        NSString *ip = [httpdns getIpByHostAsync:theRequest.URL.host];
        if (ip) {
            // 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
            NSLog(@"Get IP(%@) for host(%@) from HTTPDNS Successfully!", ip, originalHost);
            NSRange hostFirstRange = [originalUrl rangeOfString:originalHost];
            if (NSNotFound != hostFirstRange.location) {
                NSString *newUrl = [originalUrl stringByReplacingCharactersInRange:hostFirstRange withString:ip];
                NSLog(@"New URL: %@", newUrl);
                self.request.URL = [NSURL URLWithString:newUrl];
                [self.request setValue:originalHost forHTTPHeaderField:@"host"];
            }
        }
    
        if (!_session) {
            NSURLSessionConfiguration *urlSessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            if ([WXAppConfiguration customizeProtocolClasses].count > 0) {
                NSArray *defaultProtocols = urlSessionConfig.protocolClasses;
                urlSessionConfig.protocolClasses = [[WXAppConfiguration customizeProtocolClasses] arrayByAddingObjectsFromArray:defaultProtocols];
            }
            _session = [NSURLSession sessionWithConfiguration:urlSessionConfig
                                                     delegate:self
                                                delegateQueue:[NSOperationQueue mainQueue]];
            _delegates = [WXThreadSafeMutableDictionary new];
        }
    
        NSURLSessionDataTask *task = [_session dataTaskWithRequest:theRequest];
        theRequest.taskIdentifier = task;
        [_delegates setObject:delegate forKey:task];
        [task resume];
    }
    
    - (void)cancelRequest:(WXResourceRequest *)request
    {
        if ([request.taskIdentifier isKindOfClass:[NSURLSessionTask class]]) {
            NSURLSessionTask *task = (NSURLSessionTask *)request.taskIdentifier;
            [task cancel];
            [_delegates removeObjectForKey:task];
        }
    }
    
    #pragma mark - NSURLSessionTaskDelegate & NSURLSessionDataDelegate
    
    - (void)URLSession:(NSURLSession *)session
                  task:(NSURLSessionTask *)task
       didSendBodyData:(int64_t)bytesSent
        totalBytesSent:(int64_t)totalBytesSent
    totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
    {
        id<WXResourceRequestDelegate> delegate = [_delegates objectForKey:task];
        [delegate request:(WXResourceRequest *)task.originalRequest didSendData:bytesSent totalBytesToBeSent:totalBytesExpectedToSend];
    }
    
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)task
    didReceiveResponse:(NSURLResponse *)response
     completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
    {
        id<WXResourceRequestDelegate> delegate = [_delegates objectForKey:task];
        [delegate request:(WXResourceRequest *)task.originalRequest didReceiveResponse:(WXResourceResponse *)response];
        completionHandler(NSURLSessionResponseAllow);
    }
    
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)task didReceiveData:(NSData *)data
    {
        id<WXResourceRequestDelegate> delegate = [_delegates objectForKey:task];
        [delegate request:(WXResourceRequest *)task.originalRequest didReceiveData:data];
    }
    
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
    {
        id<WXResourceRequestDelegate> delegate = [_delegates objectForKey:task];
        if (error) {
            [delegate request:(WXResourceRequest *)task.originalRequest didFailWithError:error];
        }else {
            [delegate requestDidFinishLoading:(WXResourceRequest *)task.originalRequest];
        }
        [_delegates removeObjectForKey:task];
    }
    
    #ifdef __IPHONE_10_0
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
    {
        id<WXResourceRequestDelegate> delegate = [_delegates objectForKey:task];
        [delegate request:(WXResourceRequest *)task.originalRequest didFinishCollectingMetrics:metrics];
    }
    #endif
    
    - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                      forDomain:(NSString *)domain {
        /*
         * 创建证书校验策略
         */
        NSMutableArray *policies = [NSMutableArray array];
        if (domain) {
            [policies addObject:(__bridge_transfer id) SecPolicyCreateSSL(true, (__bridge CFStringRef) domain)];
        } else {
            [policies addObject:(__bridge_transfer id) SecPolicyCreateBasicX509()];
        }
        /*
         * 绑定校验策略到服务端的证书上
         */
        SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef) policies);
        /*
         * 评估当前serverTrust是否可信任,
         * 官方建议在result = kSecTrustResultUnspecified 或 kSecTrustResultProceed
         * 的情况下serverTrust可以被验证通过,https://developer.apple.com/library/ios/technotes/tn2232/_index.html
         * 关于SecTrustResultType的详细信息请参考SecTrust.h
         */
        SecTrustResultType result;
        SecTrustEvaluate(serverTrust, &result);
        return (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
    }
    
    #pragma mark - NSURLSessionTaskDelegate
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *_Nullable))completionHandler {
        if (!challenge) {
            return;
        }
        NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        NSURLCredential *credential = nil;
        /*
         * 获取原始域名信息。
         */
        NSString *host = [[self.request allHTTPHeaderFields] objectForKey:@"host"];
        if (!host) {
            host = self.request.URL.host;
        }
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:host]) {
                disposition = NSURLSessionAuthChallengeUseCredential;
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            } else {
                disposition = NSURLSessionAuthChallengePerformDefaultHandling;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
        // 对于其他的challenges直接使用默认的验证方案
        completionHandler(disposition, credential);
    }
    
    @end

旧版本Weex SDK实现

0.7.0为例子:

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

#import "WXNetworkDefaultImpl.h"

@interface WXNetworkCallbackInfo : NSObject

@property (nonatomic, copy) void(^sendDataCallback)(int64_t, int64_t);
@property (nonatomic, copy) void(^responseCallback)(NSURLResponse *);
@property (nonatomic, copy) void(^receiveDataCallback)(NSData *);
@property (nonatomic, strong) NSMutableData *data;
@property (nonatomic, copy) void(^compeletionCallback)(NSData *, NSError *);

@end

@implementation WXNetworkCallbackInfo

@end

@implementation WXNetworkDefaultImpl
{
    NSMutableDictionary *_callbacks;
    NSURLSession *_session;
}

- (id)sendRequest:(NSURLRequest *)request withSendingData:(void (^)(int64_t, int64_t))sendDataCallback
                                             withResponse:(void (^)(NSURLResponse *))responseCallback
                                          withReceiveData:(void (^)(NSData *))receiveDataCallback
                                          withCompeletion:(void (^)(NSData *, NSError *))compeletionCallback
{
    WXNetworkCallbackInfo *info = [WXNetworkCallbackInfo new];
    info.sendDataCallback = sendDataCallback;
    info.responseCallback = responseCallback;
    info.receiveDataCallback = receiveDataCallback;
    info.compeletionCallback = compeletionCallback;

    if (!_session) {
        _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
                                                 delegate:self
                                            delegateQueue:[NSOperationQueue mainQueue]];
    }

    NSURLSessionDataTask *task = [_session dataTaskWithRequest:request];
    if (!_callbacks) {
        _callbacks = [NSMutableDictionary dictionary];
    }
    [_callbacks setObject:info forKey:task];
    [task resume];

    return task;
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                                didSendBodyData:(int64_t)bytesSent
                                 totalBytesSent:(int64_t)totalBytesSent
                       totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
    WXNetworkCallbackInfo *info = [_callbacks objectForKey:task];
    if (info.sendDataCallback) {
        info.sendDataCallback(totalBytesSent, totalBytesExpectedToSend);
    }
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)task
                                 didReceiveResponse:(NSURLResponse *)response
                                  completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
    WXNetworkCallbackInfo *info = [_callbacks objectForKey:task];
    if (info.responseCallback) {
        info.responseCallback(response);
    }
    completionHandler(NSURLSessionResponseAllow);
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)task didReceiveData:(NSData *)data
{
    WXNetworkCallbackInfo *info = [_callbacks objectForKey:task];
    if (info.receiveDataCallback) {
        info.receiveDataCallback(data);
    }

    NSMutableData *mutableData = info.data;
    if (!mutableData) {
        mutableData = [NSMutableData new];
        info.data = mutableData;
    }

    [mutableData appendData:data];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    WXNetworkCallbackInfo *info = [_callbacks objectForKey:task];
    if (info.compeletionCallback) {
        info.compeletionCallback(info.data, error);
    }
    [_callbacks removeObjectForKey:task];
}

@end

<image>网络请求+HTTPDNS

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

  1. 自定义图片请求适配器,实现IWXImgLoaderAdapter接口

    #import "WXImgLoaderProtocol.h"
    
    @interface WXImgLoaderDefaultImpl : NSObject<WXImgLoaderProtocol, WXModuleProtocol>
    @end
  2. WEEX初始化时注册该图片适配器:

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

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