关于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];
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容