WKWebView
的優(yōu)點這里不做過多介紹,主要說一下最近解決WKWebView
的post
請求丟失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ā)起的reques
t請求是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ù)request
的method
方法是否為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ù)request
的method
方法是否為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];
}