美文网首页iOS网络请求iOS Developer其他
关于WKWebView的post请求丢失body问题的解决方案

关于WKWebView的post请求丢失body问题的解决方案

作者: a_只羊 | 来源:发表于2019-10-29 19:22 被阅读0次

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

    相关文章

      网友评论

        本文标题:关于WKWebView的post请求丢失body问题的解决方案

        本文链接:https://www.haomeiwen.com/subject/epggvctx.html