91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

NSURLProtocol攔截NSURLSession請求時body丟失問題的解決方案是什么

發布時間:2021-12-06 13:58:29 來源:億速云 閱讀:262 作者:柒染 欄目:云計算

NSURLProtocol攔截NSURLSession請求時body丟失問題的解決方案是什么,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

“IP直連方案”主要在于解決DNS污染、省去DNS解析時間,通常情況下我們可以在項目中使用 NSURLProtocol 攔截 NSURLSession 請求,下面將支持 Post 請求中面臨的一個挑戰,以及應對策略介紹一下。

“IP直連方案”主要在于解決DNS污染、省去DNS解析時間,通常情況下我們可以在項目中使用 NSURLProtocol 攔截 NSURLSession 請求,下面將支持 Post 請求中面臨的一個挑戰,以及應對策略介紹一下:

在支持POST請求過程中會遇到丟失 body的 問題,有以下幾種解決方法:

方案如下:

  1. 換用 NSURLConnection

  2. 將 body 放進 Header 中

  3. 使用 HTTPBodyStream 獲取 body,并賦值到 body 中

  4. 換用 Get 請求,不使用 Post 請求。

對方案做以下分析

  • 換用 NSURLConnection。NSURLConnection 與 NSURLSession 相比會遇到較多的性能問題,同時Apple的一些新特性也無法使用,終究會被淘汰,不作考慮。

  • body放header的方法,2M以下沒問題,超過2M會導致請求延遲,超過 10M 就直接 Request timeout。而且無法解決 Body 為二進制數據的問題,因為Header里都是文本數據。

  • 換用 Get 請求,不使用 Post 請求。這個也是可行的,但是畢竟對請求方式有限制,終究還是要解決 Post 請求所存在的問題。如果是基于舊項目做修改,則侵入性太大。這種方案適合新的項目。

  • 另一種方法是我們下面主要要講的,使用 HTTPBodyStream 獲取 body,并賦值到 body 中,具體的代碼如下,可以解決上面提到的問題:

//
//  NSURLRequest+CYLNSURLProtocolExtension.h
//
//
//  Created by ElonChan on 28/07/2017.
//  Copyright ? 2017 ChenYilong. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface NSURLRequest (CYLNSURLProtocolExtension)

- (NSURLRequest *)cyl_getPostRequestIncludeBody;

@end



//
//  NSURLRequest+CYLNSURLProtocolExtension.h
//
//
//  Created by ElonChan on 28/07/2017.
//  Copyright ? 2017 ChenYilong. All rights reserved.
//

#import "NSURLRequest+CYLNSURLProtocolExtension.h"

@implementation NSURLRequest (CYLNSURLProtocolExtension)

- (NSURLRequest *)cyl_getPostRequestIncludeBody {
    return [[self cyl_getMutablePostRequestIncludeBody] copy];
}

- (NSMutableURLRequest *)cyl_getMutablePostRequestIncludeBody {
    NSMutableURLRequest * req = [self mutableCopy];
    if ([self.HTTPMethod isEqualToString:@"POST"]) {
        if (!self.HTTPBody) {
            NSInteger maxLength = 1024;
            uint8_t d[maxLength];
            NSInputStream *stream = self.HTTPBodyStream;
            NSMutableData *data = [[NSMutableData alloc] init];
            [stream open];
            BOOL endOfStreamReached = NO;
            //不能用 [stream hasBytesAvailable]) 判斷,處理圖片文件的時候這里的[stream hasBytesAvailable]會始終返回YES,導致在while里面死循環。
            while (!endOfStreamReached) {
                NSInteger bytesRead = [stream read:d maxLength:maxLength];
                if (bytesRead == 0) { //文件讀取到最后
                    endOfStreamReached = YES;
                } else if (bytesRead == -1) { //文件讀取錯誤
                    endOfStreamReached = YES;
                } else if (stream.streamError == nil) {
                    [data appendBytes:(void *)d length:bytesRead];
                }
            }
            req.HTTPBody = [data copy];
            [stream close];
        }
        
    }
    return req;
}
@end

上面是我給出的實現,這里注意,剛開始有人做過這樣的實現:

- (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];
        }
    }
}

這個實現的問題在于:不能用 [stream hasBytesAvailable]) 判斷,處理圖片文件的時候這里的[stream hasBytesAvailable]會始終返回YES,導致在while里面死循環。

Apple的文檔也說得很清楚:

   // 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;

給出了實現,下面介紹下使用方法:

在用于攔截請求的 NSURLProtocol 的子類中實現方法 +canonicalRequestForRequest: 并處理 request 對象:

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

下面介紹下相關方法的作用:

//NSURLProtocol.h

/*! 
   @method canInitWithRequest:
   @abstract This method determines whether this protocol can handle
   the given request.
   @discussion A concrete subclass should inspect the given request and
   determine whether or not the implementation can perform a load with
   that request. This is an abstract method. Sublasses must provide an
   implementation.
   @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 given
   request.
   @discussion It is up to each concrete protocol implementation to
   define what "canonical" means. However, a protocol should
   guarantee that the same input request always yields the same
   canonical form. Special consideration should be given when
   implementing this method since the canonical form of a request is
   used to look up objects in the URL cache, a process which performs
   equality checks between NSURLRequest objects.
   <p>
   This is an abstract method; sublasses must provide an
   implementation.
   @param request A request to make canonical.
   @result The canonical form of the given request. 
*/
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;

翻譯下:

//NSURLProtocol.h
/*!
*  @method:創建NSURLProtocol實例,NSURLProtocol注冊之后,所有的NSURLConnection都會通過這個方法檢查是否持有該Http請求。
@parma :
@return: YES:持有該Http請求NO:不持有該Http請求
*/
+ (BOOL)canInitWithRequest:(NSURLRequest *)request

/*!
*  @method: NSURLProtocol抽象類必須要實現。通常情況下這里有一個最低的標準:即輸入輸出請求滿足最基本的協議規范一致。因此這里簡單的做法可以直接返回。一般情況下我們是不會去更改這個請求的。如果你想更改,比如給這個request添加一個title,組合成一個新的http請求。
@parma: 本地HttpRequest請求:request
@return:直接轉發
*/

+ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest *)request

簡單說:

  • +[NSURLProtocol canInitWithRequest:] 負責篩選哪些網絡請求需要被攔截

  • +[NSURLProtocol canonicalRequestForRequest:] 負責對需要攔截的網絡請求NSURLRequest 進行重新構造。

這里有一個注意點:+[NSURLProtocol canonicalRequestForRequest:] 的執行條件是 +[NSURLProtocol canInitWithRequest:] 返回值為 YES

注意在攔截 NSURLSession 請求時,需要將用于攔截請求的 NSURLProtocol 的子類添加到 NSURLSessionConfiguration 中,用法如下:

    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSArray *protocolArray = @[ [CYLURLProtocol class] ];
    configuration.protocolClasses = protocolArray;
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];

換用其他提供了SNI字段配置接口的更底層網絡庫

如果使用第三方網絡庫:curl, 中有一個 -resolve 方法可以實現使用指定 ip 訪問 https 網站,iOS 中集成 curl 庫,參考 curl文檔 ;

另外有一點也可以注意下,它也是支持 IPv6 環境的,只需要你在 build 時添加上 --enable-ipv6 即可。

curl 支持指定 SNI 字段,設置 SNI 時我們需要構造的參數形如: {HTTPS域名}:443:{IP地址}

假設你要訪問. www.example.org ,若IP為 127.0.0.1 ,那么通過這個方式來調用來設置 SNI 即可:

curl * --resolve 'www.example.org:443:127.0.0.1'

iOS CURL 庫

使用libcurl 來解決,libcurl / cURL 至少 7.18.1 (2008年3月30日) 在 SNI 支持下編譯一個 SSL/TLS 工具包,curl 中有一個 --resolve 方法可以實現使用指定ip訪問https網站。

在iOS實現中,代碼如下

   //{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 是結構體類型hosts_list,可以設置多個IP與Host之間的映射關系。curl_easy_setopt方法中傳入CURLOPT_RESOLVE 將該映射設置到 HTTPS 請求中。

這樣就可以達到設置SNI的目的。

我在這里寫了一個 Demo:CYLCURLNetworking,里面包含了編譯好的支持 IPv6 的 libcurl 包,演示了下如何通過curl來進行類似NSURLSession。

參考鏈接:

  • Apple - Communicating with HTTP Servers

  • Apple - HTTPS Server Trust Evaluation - Server Name Failures

  • Apple - HTTPS Server Trust Evaluation - Trusting One Specific Certificate

  • [《HTTPDNS > 最佳實踐 > HTTPS(含SNI)業務場景“IP直連”方案說明
    HTTPS(含SNI)業務場景“IP直連”方案說明》]( https://help.aliyun.com/document_detail/30143.html?spm=5176.doc30141.6.591.A8B1d3 )

  • 《在 curl 中使用指定 ip 來進行請求 https》

  • 支持SNI與WebView的 alicloud-ios-demo

  • 《SNI: 實現多域名虛擬主機的SSL/TLS認證》

補充說明

注意以上討論不涉及 WKWebView 中攔截 NSURLSession 請求的 body 丟失問題。

文中提到的幾個概念:

概念解釋舉例
host可以是 IP 也可以是 FQDN。www.xxx.com 或 1.1.1.1
FQDNfully qualified domain name,由主機名和域名兩部分組成www.xxx.com
域名域名分為全稱和簡稱,全稱就是FQDN、簡稱就是 FQDN 不包括主機名的部分比如:xxx.com ,也就是www.xxx.com 這個 FQDN 中,www 是主機名,xxx.com 是域名。

文中部分提到的域名,如果沒有特殊說明均指的是 FQDN。

關于NSURLProtocol攔截NSURLSession請求時body丟失問題的解決方案是什么問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

韩城市| 普格县| 嘉兴市| 敦化市| 贡觉县| 大安市| 许昌市| 诏安县| 涞水县| 巴楚县| 南雄市| 贺州市| 长泰县| 鄂伦春自治旗| 双牌县| 万年县| 武邑县| 米林县| 泰安市| 东城区| 瑞金市| 乌鲁木齐市| 吉安县| 剑阁县| 西和县| 土默特右旗| 水城县| 商南县| 读书| 赤城县| 常熟市| 沈阳市| 寻乌县| 南召县| 龙海市| 溧水县| 绥化市| 行唐县| 旬阳县| 新津县| 松原市|