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

WKWebView的優(yōu)點這里不做過多介紹,主要說一下最近解決WKWebViewpost請求丟失body問題的解決方案菠净。
WKWebView 通過loadrequest方法加載Post請求會丟失請求體(body)中的內容埠偿,進而導致服務器拿不到body中的內容的問題的發(fā)生。這個問題的產生主要是因為WKWebView的網(wǎng)絡請求的進程與APP不是同一個進程霍转,所以網(wǎng)絡請求的過程是這樣的:
由APP所在的進程發(fā)起request宗收,然后通過IPC通信(進程間通信)將請求的相關信息(請求頭、請求行论颅、請求體等)傳遞給webkit網(wǎng)絡線進程接收包裝哎垦,進行數(shù)據(jù)的HTTP請求囱嫩,最終再進行IPC的通信回傳給APP所在的進程的恃疯。這里如果發(fā)起的request請求是post請求的話,由于要進行IPC數(shù)據(jù)傳遞墨闲,傳遞的請求體body中根據(jù)系統(tǒng)調度今妄,將其舍棄,最終在WKWebView網(wǎng)絡進程接受的時候請求體body中的內容變成了空鸳碧,導致此種情況下的服務器獲取不到請求體盾鳞,導致問題的產生。
為了能夠獲取POST方法請求之后的body內容瞻离,這兩天整理了一些解決方案腾仅,大致分為三種:

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

三種方法中验辞,我采用了第三種方案稿黄,這里說一下第三種方案的實現(xiàn)方式,大致分為三步:

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

以下是具體的實現(xiàn)方式:

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

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

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

//包裝請求頭內容
- (WKNavigation *)loadRequest:(NSURLRequest *)request{
    NSLog(@"發(fā)起請求:%@ method:%@",request.URL.absoluteString,request.HTTPMethod);
    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    NSMutableDictionary *requestHeaders = [request.allHTTPHeaderFields mutableCopy];
    //判斷是否是POST請求,POST請求需要包裝request中的body內容到請求頭中(會有丟失body問題的產生)
    //,包裝完成之后重定向到攔截的協(xié)議中自己包裝處理請求數(shù)據(jù)內容钻哩,攔截協(xié)議是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請求并將數(shù)據(jù)回傳(以下是主要代碼)

+ (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
{
    /**
     * 接收數(shù)據(jù)
     */
    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];
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末荣刑,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子伦乔,更是在濱河造成了極大的恐慌厉亏,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烈和,死亡現(xiàn)場離奇詭異爱只,居然都是意外死亡,警方通過查閱死者的電腦和手機招刹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進店門恬试,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沥匈,“玉大人,你說我怎么就攤上這事忘渔「咛” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵畦粮,是天一觀的道長散址。 經常有香客問我,道長宣赔,這世上最難降的妖魔是什么预麸? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮儒将,結果婚禮上吏祸,老公的妹妹穿的比我還像新娘。我一直安慰自己钩蚊,他們只是感情好贡翘,可當我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著砰逻,像睡著了一般鸣驱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蝠咆,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天踊东,我揣著相機與錄音,去河邊找鬼刚操。 笑死闸翅,一個胖子當著我的面吹牛,可吹牛的內容都是我干的菊霜。 我是一名探鬼主播坚冀,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼占卧!你這毒婦竟也來了遗菠?” 一聲冷哼從身側響起联喘,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤华蜒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后豁遭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叭喜,經...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年蓖谢,在試婚紗的時候發(fā)現(xiàn)自己被綠了捂蕴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片譬涡。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖啥辨,靈堂內的尸體忽然破棺而出涡匀,到底是詐尸還是另有隱情,我是刑警寧澤溉知,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布陨瘩,位于F島的核電站,受9級特大地震影響级乍,放射性物質發(fā)生泄漏舌劳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一玫荣、第九天 我趴在偏房一處隱蔽的房頂上張望甚淡。 院中可真熱鬧,春花似錦捅厂、人聲如沸贯卦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脸侥。三九已至,卻和暖如春盈厘,著一層夾襖步出監(jiān)牢的瞬間睁枕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工沸手, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留外遇,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓契吉,卻偏偏與公主長得像跳仿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子捐晶,可洞房花燭夜當晚...
    茶點故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內容