******科普片**
1、DNS劫持的危害
不知道大家有沒有發(fā)現(xiàn)這樣一個現(xiàn)象,在打開一些網(wǎng)頁的時候會彈出一些與所瀏覽網(wǎng)頁不相關(guān)的內(nèi)容比如這樣奇(se)怪(qing)的東西
或者這樣
2、什么是DNS解析
現(xiàn)在假如我們訪問一個網(wǎng)站www.baidu.com從按下回車到百度頁面顯示到我們的電腦上會經(jīng)歷如下幾個步驟
- 1:計(jì)算機(jī)會向我們的運(yùn)營商(移動、電信、聯(lián)通等)發(fā)出打開www.baidu.com的請求。
- 2:運(yùn)營商收到請求后會到自己的DNS服務(wù)器中找www.baidu.com這個域名所對應(yīng)的服務(wù)器的IP地址(也就是百度的服務(wù)器的IP地址),這里比如是180.149.132.47。
- 3:運(yùn)營商用第二步得到的IP地址去找到百度的服務(wù)器請求得到數(shù)據(jù)后返回給我們。
其中第二步就是我們所說的DNS解析過程,域名和IP地址的關(guān)系其實(shí)就是我們的身份證號和姓名的關(guān)系,都是來標(biāo)記一個人或者是一個網(wǎng)站的,只是IP地址\身份證號只是一串沒有意義的數(shù)字,辨識度低,又不好記,所以就會在IP上加上一個域名以便區(qū)分,或是做的更加個性化,但是如果真的要來準(zhǔn)確的區(qū)分還是要靠身份證號碼或者是IP的,所以DNS解析就應(yīng)運(yùn)而生了。
3:什么是DNS劫持
DNS劫持,是指在DNS解析過程中攔截域名解析的請求,然后做一些自己的處理,比如返回假的IP地址或者什么都不做使請求失去響應(yīng),其效果就是對特定的網(wǎng)絡(luò)不能反應(yīng)或訪問的是假網(wǎng)址。根本原因就是以下兩點(diǎn):
- 1:惡意攻擊,攔截運(yùn)營商的解析過程,把自己的非法東西嵌入其中。
- 2:運(yùn)營商為了利益或者一些其他的因素,允許一些第三方在自己的鏈接里打打廣告之類的。
4:防止DNS劫持
了解了DNS劫持的相關(guān)資料后我們就知道了,防止NDS劫持就要從第二步入手,因?yàn)镈NS解析過程是運(yùn)營商來操作的,我們不能去干涉他們,不然我們也就成了劫持者了,所以我們要做的就是在我們請求之前對我們的請求鏈接做一些修改,將我們原本的請求鏈接www.baidu.com 修改為180.149.132.47,然后請求出去,這樣的話就運(yùn)營商在拿到我們的請求后發(fā)現(xiàn)我們直接用的就是IP地址就會直接給我們放行,而不會去走他自己DNS解析了,也就是說我們把運(yùn)營商要做的事情自己先做好了。不走他的DNS解析也就不會存在DNS被劫持的問題,從根本是解決了。
******技術(shù)篇**
5:項(xiàng)目中的實(shí)際操作
5.1:DNSPOD相關(guān)
我們知道要要把項(xiàng)目中請求的接口替換成成IP其實(shí)很簡單,URL是字符串,域名替換IP,無非就是一個字符串替換而已,的確這塊其實(shí)沒有什么技術(shù)含量,而且現(xiàn)在像阿里云(沒開源),七牛云(開源),等一些比較大的平臺在這方面也都有了比較成熟的解決方案,一個SDK,傳個普通的URL進(jìn)去就會返回一個域名被替換成IP的URL出來,也比較好用,這里要說一下IP地址的來源,如何拿到一個域名所對應(yīng)的IP呢?這里就是需要用到另一個服務(wù)——HTTPDNS,國內(nèi)比較有名的就是DNSPOD,包括阿里,七牛等也是使用他們的DNS服務(wù)來解析,就是這個
他會給我們提供一個接口,我們使用HTTP請求的方式去請求這個接口,參數(shù)帶上我們的域名,他們就會把域名對應(yīng)的IP列表返回回來。類似這樣:
///這個請求URL的結(jié)構(gòu)是固定的119.29.29.29是DNSPOD固定的服務(wù)器地址,ttl參數(shù)的意思是返回結(jié)果是否帶ttl是個BOOL,dn就是我們需要解析的域名,id就是我們在dnspod上注冊時候他給我們的一個KEY
NSString *url = [NSString stringWithFormat:@"http://119.29.29.29/d?ttl=1&dn=www.baidu.com&id=KEY"];
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
NSData * data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&networkError];
這里使用同步還是異步都是可以的,具體根據(jù)你們業(yè)務(wù)需求。
5.2:項(xiàng)目中的使用
其實(shí)dnspod最難的部分是接入的部分,因?yàn)椴煌腁PP不同的網(wǎng)絡(luò)環(huán)境會導(dǎo)致各種各樣的問題,如果你是一個新的項(xiàng)目那么接入難度會大大降低,因?yàn)槟阃耆梢宰约悍庋b一套網(wǎng)絡(luò)請求,把DNS解析相關(guān)的邏輯都封裝到自己的網(wǎng)絡(luò)請求中,這樣你就可以得到APP所有的網(wǎng)絡(luò)層的控制權(quán),想干什么就干什么,但是如果是在一個已經(jīng)比較完善的APP中加入DNS防劫持的話那就是比較困難,因?yàn)槟悴荒苣玫剿芯W(wǎng)絡(luò)請求的控制權(quán)這篇文章中我主要使用是NSURLProtocol + Runtime hook方式來處理這些東西的,NSURLProtocol屬于iOS黑魔法的一種可以攔截任何從APP的 URL Loading System系統(tǒng)中發(fā)出的請求,其中包括如下
- File Transfer Protocol (ftp://)
- Hypertext Transfer Protocol (http://)
- Hypertext Transfer Protocol with encryption (https://)
- Local file URLs (file:///)
- Data URLs (data://)
如果你的請求不在以上列表中就不能進(jìn)行攔截了,比如WKWebview,AVPlayer(比較特殊,雖然請求也是http/https但是就是不走這套系統(tǒng),蘋果爸爸就是這樣~)等,其實(shí)對于正常來說光用已經(jīng)NSURLProtocol足夠了。
??NSURLProtocol這個類我們不能直接使用,我們需要自己創(chuàng)建一個他的子類然后在我們的子類中操作他們像這樣
// 注冊自定義protocol
[NSURLProtocol registerClass:[CustomURLProtocol class]];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.protocolClasses = @[[CustomURLProtocol class]];
在這個類中我們可以攔截到請求,然后進(jìn)行處理。這個類中有四個非常重要的方法
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
- (void)startLoading;
//對于攔截的請求,NSURLProtocol對象在停止加載時調(diào)用該方法
- (void)stopLoading;
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
通過返回值來告訴NSUrlProtocol對進(jìn)來的請求是否攔截,比如我只攔截HTTP的,或者是某個域名的請求之類
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
如果上面的方法返回YES那么request會傳到這里,這個地方通常不做處理 直接返回request
- (void)startLoading;
這個地方就是對我們攔截的請求做一些處理,我們文中所做的IP對域名的替換就在這里進(jìn)行,處理完之后將請求轉(zhuǎn)發(fā)出去,比如這樣
- (void)startLoading {
///其中customRequest是處理過的請求(域名替換后的)
NSURLSession *session = [NSURLSession sessionWithConfiguration:[[NSURLSessionConfiguration alloc] init] delegate:self delegateQueue:nil];
NSURLSessionDataTask *task = [session dataTaskWithRequest:customRequest];
[task resume];
}
你可以在 - startLoading 中使用任何方法來對協(xié)議對象持有的 request 進(jìn)行轉(zhuǎn)發(fā),包括 NSURLSession、 NSURLConnection 甚至使用 AFNetworking 等網(wǎng)絡(luò)庫,只要你能在回調(diào)方法中把數(shù)據(jù)傳回 client,幫助其正確渲染就可以,比如這樣:
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[[self client] URLProtocol:self didLoadData:data];
}
client在后面會有講解。
- (void)stopLoading;
請求完畢后調(diào)用
大概的執(zhí)行流程是這樣
在NSURLProtocol中有一個貫穿始終的變量
/*!
@method client
@abstract Returns the NSURLProtocolClient of the receiver.
@result The NSURLProtocolClient of the receiver.
*/
@property (nullable, readonly, retain) id <NSURLProtocolClient> client;
你可以認(rèn)為是這個是請求的發(fā)送者,打個比方,A想給B發(fā)送一個消息,由于距離遙遠(yuǎn)于是A去了郵局,A把消息內(nèi)容告訴了郵局,并且A在郵局登記了自己名字方便B有反饋的時候郵局來通知A查收。這個例子中郵局就是NSURLProtocol,A在郵局登記的名字就是client。所有的 client 都實(shí)現(xiàn)了 NSURLProtocolClient 協(xié)議,協(xié)議的作用就是在 HTTP 請求發(fā)出以及接受響應(yīng)時向其它對象傳輸數(shù)據(jù):
@protocol NSURLProtocolClient <NSObject>
...
- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy;
- (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data;
- (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol;
...
@end
當(dāng)然這個協(xié)議中還有很多其他的方法,比如 HTTPS 驗(yàn)證、重定向以及響應(yīng)緩存相關(guān)的方法,你需要在合適的時候調(diào)用這些代理方法,對信息進(jìn)行傳遞。
到此正常情況下的DNS的解析過程已經(jīng)結(jié)束,如果你發(fā)現(xiàn)按照如上操作之后并沒有達(dá)到預(yù)期效果那么請往下看,(通常情況下完成以上操作 原有的URL的就會變成http://123.456.789.123/XXX/XXX/XXX的格式。如果發(fā)現(xiàn)請求不成功就往下看吧)
6:遇到的坑點(diǎn)
6.1:我們知道運(yùn)營商本來是根據(jù)域名來確定一個URL的,我們將域名改為IP之后雖然不用運(yùn)營商幫我們解析了,但是運(yùn)營商在收到一串?dāng)?shù)字的時候也是懵逼狀態(tài),我們還是需要將域名傳給他們,但是不能用正常的方式傳,我們需要把原來的域名加到http請求的Header中的host字段下,根據(jù)Http協(xié)議的規(guī)定,如果在URL中無法找到域名的話就會去Header中找,這樣一來我們既把域名告訴了運(yùn)營商同時也直接制定了IP地址,這個是必須配置的,不然的話是請求不成功的。
[mutableRequest setValue:self.request.URL.host forHTTPHeaderField:@"HOST"];
加上Header再去請求就沒問題了,不過有些特殊的情況下會需要帶上cookie,同樣也是加到Header中
[mutableRequest setValue:YOUR Cookie forHTTPHeaderField:@"Cookie"];
6.2:關(guān)于AfNetworking的問題,現(xiàn)在大部分網(wǎng)絡(luò)請求是基于Afnetworking的,這里有一個坑,我們知道我們注冊CustomProtocol的時候是這樣
// 注冊自定義protocol
[NSURLProtocol registerClass:[CustomURLProtocol class]];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.protocolClasses = @[[CustomURLProtocol class]];
在系統(tǒng)的configuration加入我們的CustomProtocol,protocolClasses是一個數(shù)組里面可以放很多各種不同的CustomProtocol,我們看一下afnetworking的初始化方法。
AFHTTPSessionManager * sessionManager = [AFHTTPSessionManager manager];
我相信大家通常都會這么來創(chuàng)建,但是這里我要說下manager并不是一個單利,最后都會調(diào)到一個方法
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
.
.
.
}
大家注意第二個判斷,如果沒有傳入configuration的話他會創(chuàng)建一個默認(rèn)的,這樣以至于我們之前在configuration的protocolClasses中注冊類全部被這個新的configuration替換掉了,所以無法解析。這里我采取的辦法就是runtime hook,因?yàn)閔ook第三方的代碼并不是一個很好的辦法,所以我直接hook NSURLSession的sessionWithConfiguration方法,因?yàn)橥ㄟ^觀察Afnetworking的源碼最終都是走到這里的。Hook之后把自己的configuration換進(jìn)去,像這樣
+ (NSURLSession *)swizzle_sessionWithConfiguration:(NSURLSessionConfiguration *)configuration {
NSURLSessionConfiguration *newConfiguration = configuration;
// 在現(xiàn)有的Configuration中插入我們自定義的protocol
if (configuration) {
NSMutableArray *protocolArray = [NSMutableArray arrayWithArray:configuration.protocolClasses];
[protocolArray insertObject:[CustomProtocol class] atIndex:0];
newConfiguration.protocolClasses = protocolArray;
}
else {
newConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSMutableArray *protocolArray = [NSMutableArray arrayWithArray:configuration.protocolClasses];
[protocolArray insertObject:[CustomProtocol class] atIndex:0];
newConfiguration.protocolClasses = protocolArray;
}
return [self swizzle_sessionWithConfiguration:newConfiguration];
}
然后就完美解決了。不過要注意下系統(tǒng)的是有兩個方法的
/*
* Customization of NSURLSession occurs during creation of a new session.
* If you only need to use the convenience routines with custom
* configuration options it is not necessary to specify a delegate.
* If you do specify a delegate, the delegate will be retained until after
* the delegate has been sent the URLSession:didBecomeInvalidWithError: message.
*/
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;
這兩個方法不能確定最終會走那個,所以為了保險起見都hook下,hook的方式是一樣的
6.3:AVPlayer請求,AVPlayer是我們iOS系統(tǒng)中系統(tǒng)自帶的播放視頻的框架,用到地方也很多,但是這個是比較坑的,因?yàn)锳VPlayer雖然也有http/https/file……請求這個概念,但是AVPlayer所有的請求都不會走URL Loading System,也就是說所有由AVPlayer發(fā)出的請求都不能被我們的CustomProtocol攔截,這時候大家也許會問,不對呀,我們正常調(diào)試的時候可以被攔截到的啊。其實(shí)蘋果官方上是說AVPlayer在真機(jī)調(diào)試和模擬器調(diào)試時候走的完全不是一套策略,也就是說在模擬器運(yùn)行時候是完全正常的,可以被攔截到也可以被解析,但是在真機(jī)上面就恰恰相反了,因?yàn)槲覀冏詈筮€是以真機(jī)為準(zhǔn),所以我們采取的辦法還是hook,因?yàn)槲覀冃枰诿襟wURL傳給AVPlayer前就要將相關(guān)東西配置好,域名替換啊,加host啊之類的,所以我們要找AVPlayer的入口,先看初始化方法,我發(fā)現(xiàn)項(xiàng)目中使用一個AVURLAsset來初始化AVPlayer,那么AVURLAsset又是什么呢?繼續(xù)查到AVURLAsset的初始化方法,可以發(fā)現(xiàn)這個方法:
/*!
@method initWithURL:options:
@abstract Initializes an instance of AVURLAsset for inspection of a media resource.
@param URL
An instance of NSURL that references a media resource.
@param options
An instance of NSDictionary that contains keys for specifying options for the initialization of the AVURLAsset. See AVURLAssetPreferPreciseDurationAndTimingKey and AVURLAssetReferenceRestrictionsKey above.
@result An instance of AVURLAsset.
*/
- (instancetype)initWithURL:(NSURL *)URL options:(nullable NSDictionary<NSString *, id> *)options NS_DESIGNATED_INITIALIZER;
其中URL就是我們傳給AVPlayer播放的URL,找到目標(biāo)就Hook下就可以了,具體過程就不多說了還是字符串替換,但是有一點(diǎn)需要注意的是,我之前上文說過做完IP對域名的替換之后還需要設(shè)置下request的Host,但是這個地方只有一個URL并沒有Request該如何處理呢?其實(shí)這個方法里面的opinion參數(shù)就是處理這個的,可以添加cookie之類的類似與httpheader的東西,可以添加這幾個Key
AVF_EXPORT NSString *const AVURLAssetPreferPreciseDurationAndTimingKey NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVURLAssetReferenceRestrictionsKey NS_AVAILABLE(10_7, 5_0);
AVF_EXPORT NSString *const AVURLAssetHTTPCookiesKey NS_AVAILABLE_IOS(8_0);
AVF_EXPORT NSString *const AVURLAssetAllowsCellularAccessKey NS_AVAILABLE_IOS(10_0);
但是并沒有發(fā)現(xiàn)和Host相關(guān)的Key,其實(shí)這個key是有的就是AVURLAssetHTTPHeaderFieldsKey只是因?yàn)檫@個Key沒暴露出來。這個地方不太確定是不是蘋果的私有API,網(wǎng)上查了大量的資料也沒有個說法,甚至我親自去蘋果開發(fā)者去問,蘋果也沒有給任何答復(fù),各種說法都有,具體使用的話就是
[self swizzle_initWithURL:videoURL options:@{AVURLAssetHTTPHeaderFieldsKey : @{@"Host":host}}]
這樣使用是沒有任何問題的,但是畢竟是沒有暴露出來的方法,我們不能這樣明目張膽的使用,其實(shí)對于字符串來說還是比較好規(guī)避的,只要不要明文出現(xiàn)這個KEY就可以,我在這里使用了一個加密,吧key變成密文然后這個地方通過解密獲取,就像這樣:
//加密后的KEY
const NSString * headerKey = @"35905FF45AFA4C579B7DE2403C7CA0CCB59AA83D660E60C9D444AFE13323618F";
.
.
.
//getRequestHeaderKey方法為解密方法
return [self swizzle_initWithURL:videoURL options:@{[self getRequestHeaderKey] : @{@"Host":host}}];
這樣之后就大功告成了,AVPlayer可以在DNS被劫持的情況下播放了,
6.4:POST請求這塊也算是一個大坑,我們知道http的post請求會包含一個body體,里面包含我們需要上傳的參數(shù)等一些資料,對于POST請求我們的NSURLProtocol是可以正常攔截的,但是我們攔截之后發(fā)現(xiàn)無論怎么樣我們獲得的body體都為nil!后來查了一些資料發(fā)下又是蘋果爸爸在做手腳。NSURLProtocol在攔截NSURLSession的POST請求時不能獲取到Request中的HTTPBody,這個貌似早就國外的論壇上傳開了,但國內(nèi)好像還鮮有人知,據(jù)蘋果官方的解釋是Body是NSData類型,即可能為二進(jìn)制內(nèi)容,而且還沒有大小限制,所以可能會很大,為了性能考慮,索性就攔截時就不拷貝了(內(nèi)流滿面臉)。為了解決這個問題,我們可以通過把Body數(shù)據(jù)放到Header中,不過Header的大小好像是有限制的,我試過2M是沒有問題,不過超過10M就直接Request timeout了。。。而且當(dāng)Body數(shù)據(jù)為二進(jìn)制數(shù)據(jù)時這招也沒轍了,因?yàn)镠eader里都是文本數(shù)據(jù),另一種方案就是用一個NSDictionary或NSCache保存沒有請求的Body數(shù)據(jù),用URL為key,最后方法就是別用NSURLSession,老老實(shí)實(shí)用古老的NSURLConnection算了。。。你以為這么就結(jié)束了嗎?并沒有,后來查了大量的資料發(fā)現(xiàn),既然post請求的httpbody沒有蘋果復(fù)制下來,那我們就不用httpbody,我們再往底層去看就會發(fā)現(xiàn)HTTPBodyStream這個東西我們可以通過他來獲取請求的body體具體代嗎如下
#pragma mark -
#pragma mark 處理POST請求相關(guān)POST 用HTTPBodyStream來處理BODY體
- (NSMutableURLRequest *)handlePostRequestBodyWithRequest:(NSMutableURLRequest *)request {
NSMutableURLRequest * req = [request mutableCopy];
if ([request.HTTPMethod isEqualToString:@"POST"]) {
if (!request.HTTPBody) {
uint8_t d[1024] = {0};
NSInputStream *stream = request.HTTPBodyStream;
NSMutableData *data = [[NSMutableData alloc] init];
[stream open];
while ([stream hasBytesAvailable]) {
NSInteger len = [stream read:d maxLength:1024];
if (len > 0 && stream.streamError == nil) {
[data appendBytes:(void *)d length:len];
}
}
req.HTTPBody = [data copy];
[stream close];
}
}
return req;
}
這樣之后的req就是攜帶了body體的request啦,可以愉快地做post請求啦。
6.5:WKWebview是新出的瀏覽器控件,這里就不多說了,WKWebview不走URL Loading System,所以也不會被攔截,不過也是有辦法的,但是因?yàn)檫@次項(xiàng)目中沒有用到,所以沒有過多的去研究,后續(xù)我會寫一篇關(guān)于這個博客,不是很難,依舊是runtime大法。
6.6:SNI環(huán)境,這個可是坑了我好久好久的東西,所以我會放在最后去說,SNI環(huán)境因?yàn)樯婕暗阶C書驗(yàn)證所以是在https的基礎(chǔ)上來說的,SNI(Server Name Indication)是為了解決一個服務(wù)器使用多個域名和證書的擴(kuò)展。一句話簡述它的工作原理就是,在連接到服務(wù)器建立SSL鏈接之前先發(fā)送要訪問站點(diǎn)的域名(Hostname),這樣服務(wù)器根據(jù)這個域名返回一個合適的證書。其實(shí)關(guān)于SNI環(huán)境在這里就不過多解釋,阿里云文檔有很明白的解釋,同時他也有安卓和iOS在SNI環(huán)境下的處理文檔,我們發(fā)現(xiàn)安卓部分寫的很詳細(xì),可是已到了iOS這邊就這樣了:
三行文字加三個鏈接就完事了。其實(shí)在遇到這個坑的時候我也查過很多相關(guān)資料,無非就是這三行話加這三個鏈接復(fù)制來復(fù)制去,沒有實(shí)質(zhì)性的進(jìn)展,大部分公司或者是項(xiàng)目沒有這么重的Httpdns需求,所以也就不會有這個環(huán)境,即使遇到了也就直接關(guān)閉httpdns了,后來只能自己去用CFNetwork一點(diǎn)點(diǎn)實(shí)現(xiàn)。具體代碼就不跟大家粘貼了因?yàn)樯婕暗揭恍┕緝?nèi)部的代碼,不過我會把我主要的參考資料發(fā)給大家。這里有個小技巧,因?yàn)槎荚谡fCFNetwork是比較底層的網(wǎng)絡(luò)實(shí)現(xiàn),好多東西需要開發(fā)者自行處理比如一些變量的釋放之類的,所以我們能少用盡量少用,因?yàn)镃fnetwork是為SNI(https)環(huán)境服務(wù),所以我們在攔截判斷的時候可以區(qū)分是用上層的網(wǎng)絡(luò)請求轉(zhuǎn)發(fā)還是用底層的cfnetwork來轉(zhuǎn)發(fā),
if ([self.request.URL.scheme isEqualToString:@"https"] ) {
//使用CFnetwork
curRequest = req;
self.task = [[CustomCFNetworkRequestTask alloc] initWithURLRequest:originalRequest swizzleRequest:curRequest delegate:self];
if (self.task) {
[self.task startLoading];
}
} else {
//使用普通網(wǎng)絡(luò)請求
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionTask *task = [self.session dataTaskWithRequest:req];
[task resume];
}
我是這么做的。
6.7:在NSURLProtocol中的那幾個類方法中是可以發(fā)送同步請求的,但是在實(shí)例方法發(fā)送同步請求就會卡死,所以實(shí)例方法中不能有任何的阻塞,進(jìn)行同步操作。不然就卡死。
7:總結(jié)
完成了以上的步驟之后你回發(fā)現(xiàn)在DNS壞掉的情況下手機(jī)里面除了微信QQ(他們也做了DNS解析)之外其他應(yīng)用都不能上網(wǎng)了但是你的App依然可以正常瀏覽網(wǎng)絡(luò)數(shù)據(jù)。這就是我最近在做的時候遇到的一些問題,有什么問題及時與我交流吧