iOS 開發(fā)中使用 NSURLProtocol 攔截 HTTP 請求

intercept

這篇文章會提供一種在 Cocoa 層攔截所有 HTTP 請求的方法卖擅,其實標(biāo)題已經(jīng)說明了攔截 HTTP 請求需要的了解的就是 NSURLProtocol在刺。

由于文章的內(nèi)容較長决摧,會分成兩部分空免,這篇文章介紹 NSURLProtocol 攔截 HTTP 請求的原理顽染,另一篇文章如何進行 HTTP Mock 介紹這個原理在 OHHTTPStubs 中的應(yīng)用草丧,它是如何 Mock(偽造)某個 HTTP 請求對應(yīng)的響應(yīng)的狸臣。

NSURLProtocol

NSURLProtocol 是蘋果為我們提供的 URL Loading System 的一部分,這是一張從官方文檔貼過來的圖片:

URL-loading-syste

官方文檔對 NSURLProtocol 的描述是這樣的:

An NSURLProtocol object handles the loading of protocol-specific URL data. The NSURLProtocol class itself is an abstract class that provides the infrastructure for processing URLs with a specific URL scheme. You create subclasses for any custom protocols or URL schemes that your app supports.

在每一個 HTTP 請求開始時昌执,URL 加載系統(tǒng)創(chuàng)建一個合適的 NSURLProtocol 對象處理對應(yīng)的 URL 請求烛亦,而我們需要做的就是寫一個繼承自 NSURLProtocol 的類,并通過 - registerClass: 方法注冊我們的協(xié)議類懂拾,然后 URL 加載系統(tǒng)就會在請求發(fā)出時使用我們創(chuàng)建的協(xié)議對象對該請求進行處理煤禽。

這樣,我們需要解決的核心問題就變成了如何使用 NSURLProtocol 來處理所有的網(wǎng)絡(luò)請求岖赋,這里使用蘋果官方文檔中的 CustomHTTPProtocol 進行介紹檬果,你可以點擊這里下載源代碼。

在這個工程中 CustomHTTPProtocol.m 是需要重點關(guān)注的文件唐断,CustomHTTPProtocol 就是 NSURLProtocol 的子類:

@interface CustomHTTPProtocol : NSURLProtocol

...

@end

現(xiàn)在重新回到需要解決的問題选脊,也就是 如何使用 NSURLProtocol 攔截 HTTP 請求?脸甘,有這個么幾個問題需要去解決:

  • 如何決定哪些請求需要當(dāng)前協(xié)議對象處理恳啥?
  • 對當(dāng)前的請求對象需要進行哪些處理?
  • NSURLProtocol 如何實例化丹诀?
  • 如何發(fā)出 HTTP 請求并且將響應(yīng)傳遞給調(diào)用者钝的?

上面的這幾個問題其實都可以通過 NSURLProtocol 為我們提供的 API 來解決,決定請求是否需要當(dāng)前協(xié)議對象處理的方法是:+ canInitWithRequest

+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    BOOL shouldAccept;
    NSURL *url;
    NSString *scheme;
    
    shouldAccept = (request != nil);
    if (shouldAccept) {
        url = [request URL];
        shouldAccept = (url != nil);
    }
    return shouldAccept;
}

因為項目中的這個方法是大約有 60 多行铆遭,在這里只粘貼了其中的一部分硝桩,只為了說明該方法的作用:每一次請求都會有一個 NSURLRequest 實例,上述方法會拿到所有的請求對象枚荣,我們就可以根據(jù)對應(yīng)的請求選擇是否處理該對象碗脊;而上面的代碼只會處理所有 URL 不為空的請求。

請求經(jīng)過 + canInitWithRequest: 方法過濾之后棍弄,我們得到了所有要處理的請求望薄,接下來需要對請求進行一定的操作疟游,而這都會在 + canonicalRequestForRequest: 中進行,雖然它與 + canInitWithRequest: 方法傳入的 request 對象都是一個痕支,但是最好不要在 + canInitWithRequest: 中操作對象颁虐,可能會有語義上的問題;所以卧须,我們需要覆寫 + canonicalRequestForRequest: 方法提供一個標(biāo)準(zhǔn)的請求對象:

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    return request;
}

這里對請求不做任何修改另绩,直接返回,當(dāng)然你也可以給這個請求加個 header花嘶,只要最后返回一個 NSURLRequest 對象就可以笋籽。

在得到了需要的請求對象之后,就可以初始化一個 NSURLProtocol 對象了:

- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id <NSURLProtocolClient>)client {
    return [super initWithRequest:request cachedResponse:cachedResponse client:client];
}

在這里直接調(diào)用 super 的指定構(gòu)造器方法椭员,實例化一個對象车海,然后就進入了發(fā)送網(wǎng)絡(luò)請求,獲取數(shù)據(jù)并返回的階段了:

- (void)startLoading {
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[[NSURLSessionConfiguration alloc] init] delegate:self delegateQueue:nil];
    NSURLSessionDataTask *task = [session dataTaskWithRequest:self.request];
    [task resume];
}

這里使用簡化了 CustomHTTPClient 中的項目代碼隘击,可以達(dá)到幾乎相同的效果侍芝。

你可以在 - startLoading 中使用任何方法來對協(xié)議對象持有的 request 進行轉(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];
}

當(dāng)然這里省略后的代碼只會保證大多數(shù)情況下的正確執(zhí)行咧栗,只是給你一個對獲取響應(yīng)數(shù)據(jù)粗略的認(rèn)知,如果你需要更加詳細(xì)的代碼虱肄,我覺得最好還是查看一下 CustomHTTPProtocol 中對 HTTP 響應(yīng)處理的代碼致板,也就是 NSURLSessionDelegate 協(xié)議實現(xiàn)的部分。

client 你可以理解為當(dāng)前網(wǎng)絡(luò)請求的發(fā)起者咏窿,所有的 client 都實現(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īng)緩存相關(guān)的方法,你需要在合適的時候調(diào)用這些代理方法稚茅,對信息進行傳遞纸淮。

如果你只是繼承了 NSURLProtocol 并且實現(xiàn)了上述方法,依然不能達(dá)到預(yù)期的效果亚享,完成對 HTTP 請求的攔截咽块,你還需要在 URL 加載系統(tǒng)中注冊當(dāng)前類:

[NSURLProtocol registerClass:self];

需要注意的是 NSURLProtocol 只能攔截 UIURLConnectionNSURLSessionUIWebView 中的請求欺税,對于 WKWebView 中發(fā)出的網(wǎng)絡(luò)請求也無能為力侈沪,如果真的要攔截來自 WKWebView 中的請求揭璃,還是需要實現(xiàn) WKWebView 對應(yīng)的 WKNavigationDelegate,并在代理方法中獲取請求亭罪。
無論是 NSURLProtocol瘦馍、NSURLConnection 還是 NSURLSession 都會走底層的 socket,但是 WKWebView 可能由于基于 WebKit应役,并不會執(zhí)行 C socket 相關(guān)的函數(shù)對 HTTP 請求進行處理情组,具體會執(zhí)行什么代碼暫時不是很清楚,如果對此有興趣的讀者箩祥,可以聯(lián)系筆者一起討論院崇。

總結(jié)

如果你只想了解如何對 HTTP 請求進行攔截撕瞧,其實看到這里就可以了竿拆,不過如果你想應(yīng)用文章中的內(nèi)容或者希望了解如何偽造 HTTP 響應(yīng),可以看下一篇文章如何進行 HTTP Mock扭弧。

References

Github Repo:iOS-Source-Code-Analyze

Follow: Draveness · Github

Source: http://draveness.me/intercept

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蕉陋,一起剝皮案震驚了整個濱河市捐凭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寺滚,老刑警劉巖柑营,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異村视,居然都是意外死亡官套,警方通過查閱死者的電腦和手機蚁孔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绞旅,“玉大人因悲,你說我怎么就攤上這事∥篮担” “怎么了顾翼?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵跪呈,是天一觀的道長耗绿。 經(jīng)常有香客問我,道長究反,這世上最難降的妖魔是什么精耐? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任恼蓬,我火速辦了婚禮处硬,結(jié)果婚禮上凿跳,老公的妹妹穿的比我還像新娘疮方。我一直安慰自己案站,他們只是感情好蟆盐,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布石挂。 她就那樣靜靜地躺著富岳,像睡著了一般窖式。 火紅的嫁衣襯著肌膚如雪萝喘。 梳的紋絲不亂的頭發(fā)上阁簸,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天醉旦,我揣著相機與錄音髓抑,去河邊找鬼吨拍。 笑死羹饰,一個胖子當(dāng)著我的面吹牛笑旺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播使兔,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼泽艘!你這毒婦竟也來了欲险?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤匹涮,失蹤者是張志新(化名)和其女友劉穎天试,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體焕盟,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡秋秤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了脚翘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灼卢。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖来农,靈堂內(nèi)的尸體忽然破棺而出鞋真,到底是詐尸還是另有隱情,我是刑警寧澤沃于,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布涩咖,位于F島的核電站,受9級特大地震影響繁莹,放射性物質(zhì)發(fā)生泄漏檩互。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一咨演、第九天 我趴在偏房一處隱蔽的房頂上張望闸昨。 院中可真熱鬧,春花似錦薄风、人聲如沸饵较。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽循诉。三九已至,卻和暖如春撇他,著一層夾襖步出監(jiān)牢的瞬間茄猫,已是汗流浹背狈蚤。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留募疮,地道東北人炫惩。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像阿浓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蹋绽,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理芭毙,服務(wù)發(fā)現(xiàn),斷路器卸耘,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • iOS網(wǎng)絡(luò)編程讀書筆記 Facade Tester客戶端門面模式的實例(被動版本化) 被動版本化退敦,所以硬編碼URL...
    melouverrr閱讀 1,606評論 3 7
  • 一、概念(載錄于:http://www.cnblogs.com/EricaMIN1987_IT/p/3837436...
    yuantao123434閱讀 8,348評論 6 152
  • 本文是逐行翻譯蚣抗,便于參照原文侈百,如有歧義或者疑問請閱讀原文比較。于 2017.1.25===============...
    Auditore閱讀 1,514評論 4 5
  • 凱文·史派西,今天锭魔,出柜了例证。 出柜沒什么,但這個出柜迷捧,貌似別有用心织咧。 他被韋恩斯坦事件卷入丑聞,被《星際迷航:發(fā)現(xiàn)...
    Sir電影閱讀 1,470評論 2 12