關于WKWebView的post請求丟失body問題的解決方案

WKWebView的優點這里不做過多介紹,主要說一下最近解決WKWebViewpost請求丟失body問題的解決方案。
WKWebView 通過loadrequest方法加載Post請求會丟失請求體(body)中的內容,進而導致服務器拿不到body中的內容的問題的發生。這個問題的產生主要是因為WKWebView的網絡請求的進程與APP不是同一個進程,所以網絡請求的過程是這樣的:
由APP所在的進程發起request,然后通過IPC通信(進程間通信)將請求的相關信息(請求頭、請求行、請求體等)傳遞給webkit網絡線進程接收包裝,進行數據的HTTP請求,最終再進行IPC的通信回傳給APP所在的進程的。這里如果發起的request請求是post請求的話,由于要進行IPC數據傳遞,傳遞的請求體body中根據系統調度,將其舍棄,最終在WKWebView網絡進程接受的時候請求體body中的內容變成了空,導致此種情況下的服務器獲取不到請求體,導致問題的產生。
為了能夠獲取POST方法請求之后的body內容,這兩天整理了一些解決方案,大致分為三種:

1.將網絡請求交由Js發起,繞開系統WKWebView的網絡的進程請求達到正常請求的目的
2.改變POST請求的方法為GET方法(有風險,不一定服務器會接受GET方法)
3.將Post請求的請求body內容放入請求的Header中,并通過URLProtocol攔截自定義協議,在攔截中通過NSConnection進行重新請求(重新包裝請求body),然后通過回調Client客戶端來傳遞數據內容

三種方法中,我采用了第三種方案,這里說一下第三種方案的實現方式,大致分為三步:

1.注冊攔截的自定義的scheme
2.重寫loadRequest()方法,根據requestmethod方法是否為POST進行URL的攔截替換
3.在URLProtocol中進行request的重新包裝(獲取請求的body內容),使用NSURLConnection進行HTTP請求并將數據回傳
這里說明一下為什么要自己去注冊自定義的scheme,而不是直接攔截https/http。主要原因是:如果注冊了https/http的攔截,那么所有的http(s)請求都會交由系統進程處理,那么此時系統進程會通過IPC的形式傳遞給實現URLProctol協議的類去處理,在通過IPC傳遞的過程中丟失body體(上面有講到),所以在攔截的時候是拿不到POST方法的請求體body的。然而并不是所有的http請求都會走loadrequest()方法(比如js中的ajax請求),所以導致一些POST請求沒有被包裝(將請求體body內容放到請求頭header)就被攔截了,進而丟失請求體body內容,問題一樣會產生。所以為了避免這樣的問題,我們需要自己去定一個scheme協議,保證不過度攔截并且能夠處理我們需要處理的POST請求內容。

以下是具體的實現方式:

1.注冊攔截的自定義的scheme

    [NSURLProtocol registerClass:NSClassFromString(@“GCURLProtocol")];
    [NSURLProtocol wk_registerScheme:@"gc"];
    [NSURLProtocol wk_registerScheme:WkCustomHttp];
    [NSURLProtocol wk_registerScheme:WkCustomHttps];

2.重寫loadRequest()方法,根據requestmethod方法是否為POST進行URL的攔截替換

//包裝請求頭內容
- (WKNavigation *)loadRequest:(NSURLRequest *)request{
    NSLog(@"發起請求:%@ method:%@",request.URL.absoluteString,request.HTTPMethod);
    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    NSMutableDictionary *requestHeaders = [request.allHTTPHeaderFields mutableCopy];
    //判斷是否是POST請求,POST請求需要包裝request中的body內容到請求頭中(會有丟失body問題的產生)
    //,包裝完成之后重定向到攔截的協議中自己包裝處理請求數據內容,攔截協議是GCURLProtocol,請自行搜索
    if ([mutableRequest.HTTPMethod isEqualToString:@"POST"] && ([mutableRequest.URL.scheme isEqualToString:@"http"] || [mutableRequest.URL.scheme isEqualToString:@"https"])) {
        NSString *absoluteStr = mutableRequest.URL.absoluteString;
        if ([[absoluteStr substringWithRange:NSMakeRange(absoluteStr.length-1, 1)] isEqualToString:@"/"]) {
            absoluteStr = [absoluteStr stringByReplacingCharactersInRange:NSMakeRange(absoluteStr.length-1, 1) withString:@""];
        }
        
        if ([mutableRequest.URL.scheme isEqualToString:@"https"]) {
            absoluteStr = [absoluteStr stringByReplacingOccurrencesOfString:@"https" withString:WkCustomHttps];
        }else{
            absoluteStr = [absoluteStr stringByReplacingOccurrencesOfString:@"http" withString:WkCustomHttp];
        }
        
        mutableRequest.URL = [NSURL URLWithString:absoluteStr];
        NSString *bodyDataStr = [[NSString alloc]initWithData:mutableRequest.HTTPBody encoding:NSUTF8StringEncoding];
        [requestHeaders addEntriesFromDictionary:@{@"httpbody":bodyDataStr}];
        mutableRequest.allHTTPHeaderFields = requestHeaders;
        
        NSLog(@"當前請求為POST請求Header:%@",mutableRequest.allHTTPHeaderFields);
        
    }
    return [super loadRequest:mutableRequest];
}

3.在URLProtocol中進行request的重新包裝(獲取請求的body內容),使用NSURLConnection進行HTTP請求并將數據回傳(以下是主要代碼)

+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    
    NSString *scheme = request.URL.scheme;
    
    if ([scheme isEqualToString:InterceptionSchemeKey]){
        
        if ([self propertyForKey:HaveDealRequest inRequest:request]) {
            NSLog(@"已經處理,放行");
            return NO;
        }
        return YES;
    }
    
    if ([scheme isEqualToString:WkCustomHttp]){
           
           if ([self propertyForKey:HaveDealWkHttpPostBody inRequest:request]) {
               NSLog(@"已經處理,放行");
               return NO;
           }
           return YES;
       }
    
    if ([scheme isEqualToString:WkCustomHttps]){
        
        if ([self propertyForKey:HaveDealWkHttpsPostBody inRequest:request]) {
            NSLog(@"已經處理,放行");
            return NO;
        }
        return YES;
    }
        
    return NO;
    
}
- (void)startLoading {
    
    //截獲 gc 鏈接的所有請求,替換成本地資源或者線上資源
    if ([self.request.URL.scheme isEqualToString:InterceptionSchemeKey]) {
        [self htmlCacheRequstLoad];
    }
    
    else if ([self.request.URL.scheme isEqualToString:WkCustomHttp] || [self.request.URL.scheme isEqualToString:WkCustomHttps]){
        [self postBodyAddLoad];
    }
    else{
        NSMutableURLRequest *newRequest = [self cloneRequest:self.request];
        NSString *urlString = newRequest.URL.absoluteString;
        [self addHttpPostBody:newRequest];
        [NSURLProtocol setProperty:@YES forKey:GCProtocolKey inRequest:newRequest];
        [self sendRequest:newRequest];
    }
    
   
}

- (void)addHttpPostBody:(NSMutableURLRequest *)redirectRequest{
    
    //判斷當前的請求是否是Post請求
    if ([self.request.HTTPMethod isEqualToString:@"POST"]) {
        NSLog(@"post請求");
        NSMutableDictionary *headerDict = [redirectRequest.allHTTPHeaderFields mutableCopy];
        NSString *body = headerDict[@"httpbody"]?:@"";
        if (body.length) {
            redirectRequest.HTTPBody = [body dataUsingEncoding:NSUTF8StringEncoding];
            NSLog(@"body:%@",body);
        }
    }
}
- (void)postBodyAddLoad{
    
    NSMutableURLRequest *cloneRequest = [self cloneRequest:self.request];
    if ([cloneRequest.URL.scheme isEqualToString:WkCustomHttps]) {
        cloneRequest.URL = [NSURL URLWithString:[cloneRequest.URL.absoluteString stringByReplacingOccurrencesOfString:WkCustomHttps withString:@"https"]];
        [NSURLProtocol setProperty:@YES forKey:HaveDealWkHttpsPostBody inRequest:cloneRequest];
    }else if ([cloneRequest.URL.scheme isEqualToString:WkCustomHttp]){
        
        cloneRequest.URL = [NSURL URLWithString:[cloneRequest.URL.absoluteString stringByReplacingOccurrencesOfString:WkCustomHttp withString:@"http"]];
        [NSURLProtocol setProperty:@YES forKey:HaveDealWkHttpPostBody inRequest:cloneRequest];
    }
    //添加body內容
    [self addHttpPostBody:cloneRequest];
    NSLog(@"請求body添加完成:%@",[[NSString alloc]initWithData:cloneRequest.HTTPBody encoding:NSUTF8StringEncoding]);
    [self sendRequest:cloneRequest];
    
}
//復制Request對象
- (NSMutableURLRequest *)cloneRequest:(NSURLRequest *)request
{
    NSMutableURLRequest *newRequest = [NSMutableURLRequest requestWithURL:request.URL cachePolicy:request.cachePolicy timeoutInterval:request.timeoutInterval];
    
    newRequest.allHTTPHeaderFields = request.allHTTPHeaderFields;
    [newRequest setValue:@"image/webp,image/*;q=0.8" forHTTPHeaderField:@"Accept"];
    
    if (request.HTTPMethod) {
        newRequest.HTTPMethod = request.HTTPMethod;
    }
    
    if (request.HTTPBodyStream) {
        newRequest.HTTPBodyStream = request.HTTPBodyStream;
    }
    
    if (request.HTTPBody) {
        newRequest.HTTPBody = request.HTTPBody;
    }
    
    newRequest.HTTPShouldUsePipelining = request.HTTPShouldUsePipelining;
    newRequest.mainDocumentURL = request.mainDocumentURL;
    newRequest.networkServiceType = request.networkServiceType;
    
    return newRequest;
}

#pragma mark - NSURLConnectionDataDelegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    /**
     * 收到服務器響應
     */
    NSURLResponse *returnResponse = response;
    [self.client URLProtocol:self didReceiveResponse:returnResponse cacheStoragePolicy:NSURLCacheStorageAllowed];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    /**
     * 接收數據
     */
    if (!self.recData) {
        self.recData = [NSMutableData new];
    }
    if (data) {
        [self.recData appendData:data];
    }
}
- (nullable NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(nullable NSURLResponse *)response
{
    /**
     * 重定向
     */
    if (response) {
        [self.client URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
    }
    return request;
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    [self.client URLProtocolDidFinishLoading:self];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    /**
     * 加載失敗
     */
    [self.client URLProtocol:self didFailWithError:error];
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容