NSURLProtocol hook網(wǎng)絡(luò)

1. NSURLProtocol類

NSURLProtocol能夠讓你去重新定義蘋果的URL加載系統(tǒng) (URL Loading System)的行為抚恒,URL Loading System里有許多類用于處理URL請(qǐng)求,比如NSURL蛤奢,NSURLRequest,NSURLConnection和NSURLSession等


image.png

可以攔截的網(wǎng)絡(luò)請(qǐng)求包括NSURLSession陶贼,NSURLConnection以及UIWebVIew,基于CFNetwork的網(wǎng)絡(luò)請(qǐng)求啤贩,以及WKWebView的請(qǐng)求是無(wú)法攔截的,WKWebView基于Webkit。

NSURLProtocol是一個(gè)抽象類拜秧,不能直接創(chuàng)建使用痹屹,需要繼承使用,使用的時(shí)候需要調(diào)用registerClass:枉氮, [NSURLProtocol registerClass:[KCURLProtocol class]]

image.png

image.png

NSURLProtocol用處:

  • 重定向網(wǎng)絡(luò)請(qǐng)求(可以解決電信的DNS域名劫持問(wèn)題)
  • 忽略網(wǎng)絡(luò)請(qǐng)求志衍,使用本地緩存
  • 自定義網(wǎng)絡(luò)請(qǐng)求的返回結(jié)果Response
  • 攔截圖片加載請(qǐng)求,轉(zhuǎn)為從本地文件加載
  • 一些全局的網(wǎng)絡(luò)請(qǐng)求設(shè)置
  • 快速進(jìn)行測(cè)試環(huán)境的切換
  • 過(guò)濾掉一些非法請(qǐng)求
  • 網(wǎng)絡(luò)的緩存處理(H5離線包 和 網(wǎng)絡(luò)圖片緩存)
  • 可以攔截UIWebView聊替,基于系統(tǒng)的NSURLConnection或者NSURLSession進(jìn)行封裝的網(wǎng)絡(luò)請(qǐng)求楼肪。目前WKWebView無(wú)法被NSURLProtocol攔截。
  • 當(dāng)有多個(gè)自定義NSURLProtocol注冊(cè)到系統(tǒng)中的話惹悄,會(huì)按照他們注冊(cè)的反向順序依次調(diào)用URL加載流程春叫。當(dāng)其中有一個(gè)NSURLProtocol攔截到請(qǐng)求的話,后續(xù)的NSURLProtocol就無(wú)法攔截到該請(qǐng)求泣港。

2. NSURLProtocol的使用主要是5個(gè)步驟:

  1. 注冊(cè)
    [NSURLProtocol registerClass:[KCURLProtocol class]];
  1. 攔截
    NSURLConnection UIWebview請(qǐng)求攔截
    + (BOOL)canInitWithRequest:(NSURLRequest *)request;
    NSURLSession的task攔截
    + (BOOL)canInitWithTask:(NSURLSessionTask *)task;
    這兩個(gè)方法是注冊(cè)后,NSURLProtocol就會(huì)通過(guò)這個(gè)方法確定參數(shù)request象缀、task是否需要被處理,YES需要被處理爷速,NO不需要被處理,可以做重定向霞怀、監(jiān)聽(tīng)
  1. 轉(zhuǎn)發(fā)
    + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
    通過(guò)該方法你可以簡(jiǎn)單的直接返回request惫东,但可以在這里修改request,比如修改header,修改host等廉沮,并返回一個(gè)新的request颓遏,這是一個(gè)抽象方法,子類必須實(shí)現(xiàn)
    - (void)startLoading;
    把處理過(guò)的request重新發(fā)送出去

4.回調(diào)
這四個(gè)方法來(lái)回調(diào)給原來(lái)發(fā)送網(wǎng)絡(luò)請(qǐng)求的地方滞时。
[self.client URLProtocol:self didFailWithError:error];
[self.client URLProtocolDidFinishLoading:self];
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[self.client URLProtocol:self didLoadData:data];

  1. 結(jié)束
    - (void)stopLoading;
    [NSURLProtocol unregisterClass:[KCURLProtocol class]];

根據(jù)request從系統(tǒng)的緩存中構(gòu)建protocol叁幢,沒(méi)有緩存可能為nil,
- (instancetype)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client
- (instancetype)initWithTask:(NSURLSessionTask *)task cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client

處理過(guò)的請(qǐng)求可以通過(guò)為請(qǐng)求設(shè)置關(guān)聯(lián),下次不再處理坪稽,防止無(wú)限循環(huán)
+ (id)propertyForKey:(NSString *)key inRequest:(NSURLRequest *)request
+ (void)setProperty:(id)value forKey:(NSString *)key inRequest:(NSMutableURLRequest *)request
+ (void)removePropertyForKey:(NSString *)key inRequest:(NSMutableURLRequest *)request

判斷2個(gè)請(qǐng)求是否相等曼玩,相等的話可以使用緩存數(shù)據(jù)
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b

3. 攔截重定向演示

3.1 UIWebView 攔截

KCURLProtocol中:

+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
    // 攔截百度的logo
    if ([[request.URL absoluteString] isEqualToString:@"https://m.baidu.com/static/index/plus/plus_logo_web.png"]) {
        return YES;
    }
    return NO;
}
- (void)startLoading
{
    // 攔截改成自己數(shù)據(jù)
    if ([[self.request.URL absoluteString] isEqualToString:@"https://m.baidu.com/static/index/plus/plus_logo_web.png"]) {
        NSString *fileName = [[NSBundle mainBundle] pathForResource:@"lufei.jpg" ofType:@""];
        NSData *data = [NSData dataWithContentsOfFile:fileName];
        [self.client URLProtocol:self didLoadData:data];//回調(diào)回去
    }
}
image.png
3.2 NSURLSession 攔截
image.png

NSURLSession需要給NSURLSessionConfiguration對(duì)象的protocolClasses屬性添加自己的協(xié)議,把系統(tǒng)的也加回去

- (void)startSessionHook
 {
    NSString *url  = @"http://www.baidu.com";
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
    
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
    NSMutableArray *arr = [NSMutableArray arrayWithCapacity:6];
    [arr addObject:[NSClassFromString(@"KCURLProtocol") class]];
    [arr addObjectsFromArray:config.protocolClasses];
    config.protocolClasses = arr;
    NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:mainQueue];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
  
    [dataTask resume];
}
+ (BOOL)canInitWithTask:(NSURLSessionTask *)task
{
    NSLog(@"task---%@",task);
    if ([task.originalRequest.URL.absoluteString isEqualToString:@"http://www.baidu.com"]) {
        return YES;
    }
    return NO;
}
//轉(zhuǎn)發(fā)
- (void)startLoading{
    
    NSMutableURLRequest *request = [self.request mutableCopy];
    if ([request.URL.absoluteString isEqualToString:@"http://www.baidu.com"]) {
        request.URL = [NSURL URLWithString:@"http://www.reibang.com/"];
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
        NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
        NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:mainQueue];
        NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
        
        [dataTask resume];
    }
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    completionHandler(NSURLSessionResponseAllow);
}
//回調(diào)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    // 打印返回?cái)?shù)據(jù)
    NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    if (dataStr) {
        NSLog(@"***截取數(shù)據(jù)***: %@", dataStr);
    }
    [self.client URLProtocol:self didLoadData:data];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    if (error) {
        [self.client URLProtocol:self didFailWithError:error];
    } else {
        [self.client URLProtocolDidFinishLoading:self];
    }
}
3.3 AFN 攔截

AFN也是基于NSURLSession窒百,我們可以新建一個(gè)NSURLSession的子類黍判,在load方法里面hook protocolClasses的getter方法,這樣所有的基于session的都能攔截測(cè)試實(shí)際上是hook __NSCFURLSessionConfiguration的getter方法篙梢,hook NSURLSessionConfiguration的getter不管用

+ (void)load
{
    Class cls = NSClassFromString(@"__NSCFURLSessionConfiguration") ?: NSClassFromString(@"NSURLSessionConfiguration");
    Method originalMethod = class_getInstanceMethod(cls, @selector(protocolClasses));
    method_setImplementation(originalMethod, class_getMethodImplementation(self, @selector(my_protocolClasses)));
}
- (NSArray<Class> *)my_protocolClasses
{
    NSMutableArray *arr = [[NSMutableArray alloc]initWithObjects:[NSClassFromString(@"KCURLProtocol") class], [NSClassFromString(@"_NSURLHTTPProtocol") class],[NSClassFromString(@"_NSURLDataProtocol") class],[NSClassFromString(@"_NSURLFTPProtocol") class],[NSClassFromString(@"_NSURLFileProtocol") class],[NSClassFromString(@"NSAboutURLProtocol") class],nil];
    return arr;
}

4. 多個(gè)NSURLProtocol

如果注冊(cè)了兩個(gè)NSURLProtocol,protocols的遍歷是反向的顷帖,也就是最后注冊(cè)的protocol會(huì)被優(yōu)先判斷。


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末渤滞,一起剝皮案震驚了整個(gè)濱河市贬墩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌妄呕,老刑警劉巖陶舞,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異趴腋,居然都是意外死亡吊说,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門优炬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)颁井,“玉大人,你說(shuō)我怎么就攤上這事蠢护⊙疟觯” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵葵硕,是天一觀的道長(zhǎng)眉抬。 經(jīng)常有香客問(wèn)我,道長(zhǎng)懈凹,這世上最難降的妖魔是什么蜀变? 我笑而不...
    開(kāi)封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮介评,結(jié)果婚禮上库北,老公的妹妹穿的比我還像新娘爬舰。我一直安慰自己,他們只是感情好寒瓦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布情屹。 她就那樣靜靜地躺著,像睡著了一般杂腰。 火紅的嫁衣襯著肌膚如雪垃你。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天喂很,我揣著相機(jī)與錄音惜颇,去河邊找鬼。 笑死恤筛,一個(gè)胖子當(dāng)著我的面吹牛官还,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播毒坛,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼望伦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了煎殷?” 一聲冷哼從身側(cè)響起屯伞,我...
    開(kāi)封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎豪直,沒(méi)想到半個(gè)月后劣摇,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弓乙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年末融,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暇韧。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡勾习,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出懈玻,到底是詐尸還是另有隱情巧婶,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布涂乌,位于F島的核電站艺栈,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏湾盒。R本人自食惡果不足惜湿右,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望罚勾。 院中可真熱鬧毅人,春花似錦漾唉、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)分衫。三九已至场刑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蚪战,已是汗流浹背牵现。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留邀桑,地道東北人瞎疼。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像壁畸,于是被迫代替她去往敵國(guó)和親贼急。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容