NSURLProtocol 拦截 NSURLSession 请求时body丢失问题解决方案探讨
摘要: “IP直連方案”主要在于解決DNS污染、省去DNS解析時(shí)間,通常情況下我們可以在項(xiàng)目中使用 NSURLProtocol 攔截 NSURLSession 請(qǐng)求,下面將支持 Post 請(qǐng)求中面臨的一個(gè)挑戰(zhàn),以及應(yīng)對(duì)策略介紹一下。
“IP直連方案”主要在于解決DNS污染、省去DNS解析時(shí)間,通常情況下我們可以在項(xiàng)目中使用 NSURLProtocol 攔截 NSURLSession 請(qǐng)求,下面將支持 Post 請(qǐng)求中面臨的一個(gè)挑戰(zhàn),以及應(yīng)對(duì)策略介紹一下:
在支持POST請(qǐng)求過(guò)程中會(huì)遇到丟失 body的 問(wèn)題,有以下幾種解決方法:
方案如下:
1.換用 NSURLConnection
2.將 body 放進(jìn) Header 中
3.使用 HTTPBodyStream 獲取 body,并賦值到 body 中
4.換用 Get 請(qǐng)求,不使用 Post 請(qǐng)求。
對(duì)方案做以下分析
- 換用 NSURLConnection。NSURLConnection 與 NSURLSession
相比會(huì)遇到較多的性能問(wèn)題,同時(shí)Apple的一些新特性也無(wú)法使用,終究會(huì)被淘汰,不作考慮。 - body放header的方法,2M以下沒(méi)問(wèn)題,超過(guò)2M會(huì)導(dǎo)致請(qǐng)求延遲,超過(guò) 10M 就直接 Request timeout。而且無(wú)法解決
Body 為二進(jìn)制數(shù)據(jù)的問(wèn)題,因?yàn)镠eader里都是文本數(shù)據(jù)。 - 換用 Get 請(qǐng)求,不使用 Post 請(qǐng)求。這個(gè)也是可行的,但是畢竟對(duì)請(qǐng)求方式有限制,終究還是要解決 Post
請(qǐng)求所存在的問(wèn)題。如果是基于舊項(xiàng)目做修改,則侵入性太大。這種方案適合新的項(xiàng)目。 - 另一種方法是我們下面主要要講的,使用 HTTPBodyStream 獲取 body,并賦值到 body
中,具體的代碼如下,可以解決上面提到的問(wèn)題:
上面是我給出的實(shí)現(xiàn),這里注意,剛開(kāi)始有人做過(guò)這樣的實(shí)現(xiàn):
- (void)cyl_handlePostRequestBody {if ([self.HTTPMethod isEqualToString:@"POST"]) {if (!self.HTTPBody) {uint8_t d[1024] = {0};NSInputStream *stream = self.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];}}self.HTTPBody = [data copy];[stream close];}} }這個(gè)實(shí)現(xiàn)的問(wèn)題在于:不能用 [stream hasBytesAvailable]) 判斷,處理圖片文件的時(shí)候這里的[stream hasBytesAvailable]會(huì)始終返回YES,導(dǎo)致在while里面死循環(huán)。
Apple的文檔也說(shuō)得很清楚:
// returns in O(1) a pointer to the buffer in 'buffer' and by reference in 'len' how many bytes are available. This buffer is only valid until the next stream operation. Subclassers may return NO for this if it is not appropriate for the stream type. This may return NO if the buffer is not available.@property (readonly) BOOL hasBytesAvailable;給出了實(shí)現(xiàn),下面介紹下使用方法:
在用于攔截請(qǐng)求的 NSURLProtocol 的子類(lèi)中實(shí)現(xiàn)方法 +canonicalRequestForRequest: 并處理 request 對(duì)象:
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {return [request cyl_getPostRequestIncludeBody]; }下面介紹下相關(guān)方法的作用:
//NSURLProtocol.h/*! @method canInitWithRequest:@abstract This method determines whether this protocol can handlethe given request.@discussion A concrete subclass should inspect the given request anddetermine whether or not the implementation can perform a load withthat request. This is an abstract method. Sublasses must provide animplementation.@param request A request to inspect.@result YES if the protocol can handle the given request, NO if not. */ + (BOOL)canInitWithRequest:(NSURLRequest *)request;/*! @method canonicalRequestForRequest:@abstract This method returns a canonical version of the givenrequest.@discussion It is up to each concrete protocol implementation todefine what "canonical" means. However, a protocol shouldguarantee that the same input request always yields the samecanonical form. Special consideration should be given whenimplementing this method since the canonical form of a request isused to look up objects in the URL cache, a process which performsequality checks between NSURLRequest objects.<p>This is an abstract method; sublasses must provide animplementation.@param request A request to make canonical.@result The canonical form of the given request. */ + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;翻譯下:
//NSURLProtocol.h /*! * @method:創(chuàng)建NSURLProtocol實(shí)例,NSURLProtocol注冊(cè)之后,所有的NSURLConnection都會(huì)通過(guò)這個(gè)方法檢查是否持有該Http請(qǐng)求。 @parma : @return: YES:持有該Http請(qǐng)求NO:不持有該Http請(qǐng)求 */ + (BOOL)canInitWithRequest:(NSURLRequest *)request/*! * @method: NSURLProtocol抽象類(lèi)必須要實(shí)現(xiàn)。通常情況下這里有一個(gè)最低的標(biāo)準(zhǔn):即輸入輸出請(qǐng)求滿足最基本的協(xié)議規(guī)范一致。因此這里簡(jiǎn)單的做法可以直接返回。一般情況下我們是不會(huì)去更改這個(gè)請(qǐng)求的。如果你想更改,比如給這個(gè)request添加一個(gè)title,組合成一個(gè)新的http請(qǐng)求。 @parma: 本地HttpRequest請(qǐng)求:request @return:直接轉(zhuǎn)發(fā) */+ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest *)request簡(jiǎn)單說(shuō):
- +[NSURLProtocol canInitWithRequest:] 負(fù)責(zé)篩選哪些網(wǎng)絡(luò)請(qǐng)求需要被攔截
- +[NSURLProtocol canonicalRequestForRequest:] 負(fù)責(zé)對(duì)需要攔截的網(wǎng)絡(luò)請(qǐng)求NSURLRequest 進(jìn)行重新構(gòu)造。
這里有一個(gè)注意點(diǎn):+[NSURLProtocol canonicalRequestForRequest:] 的執(zhí)行條件是 +[NSURLProtocol canInitWithRequest:] 返回值為 YES。
注意在攔截 NSURLSession 請(qǐng)求時(shí),需要將用于攔截請(qǐng)求的 NSURLProtocol 的子類(lèi)添加到 NSURLSessionConfiguration 中,用法如下:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];NSArray *protocolArray = @[ [CYLURLProtocol class] ];configuration.protocolClasses = protocolArray;NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];換用其他提供了SNI字段配置接口的更底層網(wǎng)絡(luò)庫(kù)
如果使用第三方網(wǎng)絡(luò)庫(kù):curl, 中有一個(gè) -resolve 方法可以實(shí)現(xiàn)使用指定 ip 訪問(wèn) https 網(wǎng)站,iOS 中集成 curl 庫(kù),參考 curl文檔 ;
另外有一點(diǎn)也可以注意下,它也是支持 IPv6 環(huán)境的,只需要你在 build 時(shí)添加上 –enable-ipv6 即可。
curl 支持指定 SNI 字段,設(shè)置 SNI 時(shí)我們需要構(gòu)造的參數(shù)形如: {HTTPS域名}:443:{IP地址}
假設(shè)你要訪問(wèn). www.example.org ,若IP為 127.0.0.1 ,那么通過(guò)這個(gè)方式來(lái)調(diào)用來(lái)設(shè)置 SNI 即可:
curl * --resolve 'www.example.org:443:127.0.0.1'iOS CURL 庫(kù)
使用libcurl 來(lái)解決,libcurl / cURL 至少 7.18.1 (2008年3月30日) 在 SNI 支持下編譯一個(gè) SSL/TLS 工具包,curl 中有一個(gè) –resolve 方法可以實(shí)現(xiàn)使用指定ip訪問(wèn)https網(wǎng)站。
在iOS實(shí)現(xiàn)中,代碼如下
//{HTTPS域名}:443:{IP地址}NSString *curlHost = ...;_hosts_list = curl_slist_append(_hosts_list, curlHost.UTF8String);curl_easy_setopt(_curl, CURLOPT_RESOLVE, _hosts_list);其中 curlHost 形如:
{HTTPS域名}:443:{IP地址}
_hosts_list 是結(jié)構(gòu)體類(lèi)型hosts_list,可以設(shè)置多個(gè)IP與Host之間的映射關(guān)系。curl_easy_setopt方法中傳入CURLOPT_RESOLVE 將該映射設(shè)置到 HTTPS 請(qǐng)求中。
這樣就可以達(dá)到設(shè)置SNI的目的。
我在這里寫(xiě)了一個(gè) Demo:CYLCURLNetworking,里面包含了編譯好的支持 IPv6 的 libcurl 包,演示了下如何通過(guò)curl來(lái)進(jìn)行類(lèi)似NSURLSession。
參考鏈接:
Apple - Communicating with HTTP Servers
Apple - HTTPS Server Trust Evaluation - Server Name Failures
Apple - HTTPS Server Trust Evaluation - Trusting One Specific Certificate
《HTTPDNS > 最佳實(shí)踐 > HTTPS(含SNI)業(yè)務(wù)場(chǎng)景“IP直連”方案說(shuō)明 HTTPS(含SNI)業(yè)務(wù)場(chǎng)景“IP直連”方案說(shuō)明》
《在 curl 中使用指定 ip 來(lái)進(jìn)行請(qǐng)求 https》
支持SNI與WebView的 alicloud-ios-demo
《SNI: 實(shí)現(xiàn)多域名虛擬主機(jī)的SSL/TLS認(rèn)證》
補(bǔ)充說(shuō)明
注意以上討論不涉及 WKWebView 中攔截 NSURLSession 請(qǐng)求的 body 丟失問(wèn)題。
文中提到的幾個(gè)概念:
文中部分提到的域名,如果沒(méi)有特殊說(shuō)明均指的是 FQDN。
原文鏈接
干貨好文,請(qǐng)關(guān)注掃描以下二維碼:
總結(jié)
以上是生活随笔為你收集整理的NSURLProtocol 拦截 NSURLSession 请求时body丢失问题解决方案探讨的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 基于TableStore/MaxComp
- 下一篇: MaxCompute_2_MaxComp