采用UIWebView时,用微信授权后进入绑定手机号页面,绑定手机号成功,然后重新生成一个页面(UIViewController主页),进入新页面销毁绑定手机号h5页面(UIViewController),主页正常显示。但是采用WKWebView,同样的处理,这个主页显示是没有绑定手机号的下载二维码页面。网上搜索到的说WKWebView的cookie需要用户注入,而UIWebView是cookie自己注入和保存。我把cookie从绑定手机号页面取出,传递到主页页面并且注入这个cookie还是不能显示主页。分析是cookie的问题,不知道获取和注入的cookie哪里出问题了,希望大神指点?没有办法,现在暂时不跳转页面,一个控制器处理所有js页面的显示了。
原因是: WKWebView 是一个多进程组件,每个WKWebView页面进程都有自己的cookie,它们向服务器发送请求时都自己带上自己的cookie,所以你在app中无论怎么拦截都发现请求中没有带cookie,实际上WKWebView页面进程肯定代了带了cookie,不然服务器返回错误。打印的cookie是:Cookie:JSESSIONID=A2B33F508E609B8208D8EA148114794E; _bl_uid=sOjwaley6yX6OFcn3nap0qt6p8dR。并且我测试发现_bl_uid有低概率没有,JSESSIONID都存在,还存在两个相同的_bl_uid带不同的值的情况。估计这就是WKWebView的cookie返回的说法吧,至于两对bl_uid键值对,估计是强制想向请求的HTTPHeaderFields注入cookie引起。而一旦注册 http(s) scheme 后,你发现你跳转的新页面就正常,并且cookie的键值对还多了一对(如:Cookie:JSESSIONID=143219E3B0D66946C4D949D50811F88C; _bl_uid=e9jqhlz96tX9Fhhv9fh94s2qtRvm; PS=o8SkWwD1RV7VYdPH_PGzgC5EYdv4。注意:这里指的是通过[NSHTTPCookieStorage sharedHTTPCookieStorage]获取到的app本地cookie, 不是通过通常decidePolicyForNavigationResponse(实际上两种情况通过该函数获取的cookie只有JSESSIONID=143219E3B0D66946C4D949D50811F88C一对键值,只有读本地cookie时不同)获取到的cookie。),fsCachedData存在大量缓存数据(注册 http(s) scheme前的app没有那么多数据),但是这样做的严重后果是post 请求 body 数据被清空(这个问题我遇到过,是现在一直真实存在的问题)。若从正常的微信授权成功h5页面A跳转的新控制器页面(h5页面)B,若在B页面加载发送请求时设置cookie,那么B页面加载失败,从B页面返回A页面,刷新A页面的相同控制器的子页面请求全部失败。我研究了三天了,想在不注册http(s) scheme 的情况下正常加载B页面成功都不可能。
WKWebView中Cookie混乱问题:按道理来说每个WKWebView都有一个单独的存储Cookies的空间,相互不影响,但是,奇妙之处就是我在一个UIViewController中生成了一个WKWebView,然后进行了一系列的网络访问后,推出并销毁这个UIViewcontroller;在下次进来的时候这个WKWebView会携带上次访问的部分Cookies。
这个原因是WKWebView会将Cookie存储到沙盒目录的文件中,下次WKWebView被实例化的时候,会去同步这个文件中的Cookies。
decidePolicyForNavigationAction函数中navigationAction.request是只读的,decidePolicyForNavigationResponse函数的navigationResponse.response也是只读的。你在这些函数中也没有办法重置请求的allHTTPHeaderFields的字段。
使用UIWebView没有这样的问题,这也许是UIWebView没有完全代替WKWebView原因之一吧!
参考文章:《【腾讯Bugly干货分享】WKWebView 那些坑》https://blog.csdn.net/tencent_bugly/article/details/54668721/。
测试使用的代码如一,它实际上及时更新cookie文件,由于WKWebView和本app不在一个进程中,它们不在一个程序空间,他们都有自己的cookie,它们两者之间资源共享需要进程间通信,你及时更新的是app空间的cookie,不能处理WKWebView的cookie及时同步到app空间,所以不能解决该问题:
//这个是网页加载完成,导航的变化
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
NSString *strRequest = [webView.URL.absoluteString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
FLDDLogVerbose(@"isHaveTelLoginPage:%d, webView.URL strRequest:%@",[AWSingleObject sharedInstance].isHaveTelLoginPage, strRequest);
// 获取加载网页的标题
self.titleLabel.text = self.wkWebView.title;
//取出cookie
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
//js函数
NSString *JSFuncString =
@"function setCookie(name,value,expires)\
{\
var oDate=new Date();\
oDate.setDate(oDate.getDate()+expires);\
document.cookie=name+'='+value+';expires='+oDate+';path=/'\
}\
function getCookie(name)\
{\
var arr = document.cookie.match(new RegExp('(^| )'+name+'=({FNXX==XXFN}*)(;|$)'));\
if(arr != null) return unescape(arr[2]); return null;\
}\
function delCookie(name)\
{\
var exp = new Date();\
exp.setTime(exp.getTime() - 1);\
var cval=getCookie(name);\
if(cval!=null) document.cookie= name + '='+cval+';expires='+exp.toGMTString();\
}";
//拼凑js字符串
NSMutableString *JSCookieString = JSFuncString.mutableCopy;
for (NSHTTPCookie *cookie in cookieStorage.cookies) {
NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", cookie.name, cookie.value];
[JSCookieString appendString:excuteJSString];
}
//执行js
[webView evaluateJavaScript:JSCookieString completionHandler:^(id obj, NSError * _Nullable error) {
NSLog(@"%@",error);
}];
}
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];
for (NSHTTPCookie *cookie in cookies) {
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
NSLog(@"cookie:%@", cookie);
}
decisionHandler(WKNavigationResponsePolicyAllow);
}
测试方法二,B页面加载出来的不是期望的cookie校验成功的页面, 由于WKWebView和本app不在一个进程中,它们不在一个程序空间,他们都有自己的cookie,它们两者之间资源共享需要进程间通信,你及时更新的是app空间的cookie,不能处理WKWebView的cookie及时同步到app空间,所以不能解决本问题:
#pragma mark - 页面加载前处理
- (void)beforePush:(NSDictionary *)params
{
[super beforePush:params];
NSDictionary *userInfo = params[MGJRouterParameterUserInfo];
if ([[userInfo safeObjectForKey:@"jsWebEntity"] isKindOfClass:[AWJsWebEntity class]]) {
AWJsWebEntity *jsWebEntity = [userInfo safeObjectForKey:@"jsWebEntity"];
NSMutableArray *cookiesArr = [NSMutableArray array];
/** 获取NSHTTPCookieStorage cookies */
NSHTTPCookieStorage * shareCookie = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in shareCookie.cookies){
[cookiesArr addObject:cookie];
}
self.cookiesArr = cookiesArr;
// self.cookies= [userInfo safeObjectForKey:@"cookies"];
[self loadWebURLSring:jsWebEntity.url];
}
}
#pragma mark ================ 加载方式 ================
- (void)webViewloadURLType{
// NSMutableURLRequest * Request_zsj = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.URLString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
NSMutableURLRequest *Request_zsj = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.URLString]];
NSString *cookie = [self readCurrentCookieWithDomain:self.URLString];
//cookie = [NSString stringWithFormat:@"%@;PS=o8SkWwD1RV7VYdPH_PGzgC5EYdv4", cookie];
[Request_zsj addValue:cookie forHTTPHeaderField:@"Cookie"];
NSString *cookieSting = @"";
for (NSHTTPCookie *cookie in self.cookiesArr){
if(!isEmptyString(cookieSting))
{
cookieSting = [NSString stringWithFormat:@"%@; %@=%@",cookieSting, cookie.name,cookie.value];
}
else
{
cookieSting = [NSString stringWithFormat:@"%@=%@",cookie.name,cookie.value];
}
}
// [Request_zsj setValue:cookieSting forHTTPHeaderField:@"Cookie"];
NSLog(@"Cookie:%@", cookieSting);
[Request_zsj setValue:cookieSting forHTTPHeaderField:@"Cookie"];
// [Request_zsj setValue:@"Mozilla/5.0 (iPhone; CPU iPhone OS 11_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15G77Yixiangweipai/0.0.1" forHTTPHeaderField:@"User-Agent"];
NSLog(@"task.url:%@ \n currentRequest.allHTTPHeaderFields:%@", [NSString stringWithFormat:@"%@", Request_zsj.URL], Request_zsj.allHTTPHeaderFields);
// WKHTTPCookieStore *cookieStore = self.wkWebView.configuration.websiteDataStore.httpCookieStore;
// [cookieStore setCookie:self.cookies completionHandler:nil];
//加载网页
[self.wkWebView loadRequest:Request_zsj];
}
- (NSString *)readCurrentCookieWithDomain:(NSString *)domainStr{
NSHTTPCookieStorage*cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSMutableString * cookieString = [[NSMutableString alloc]init];
for (NSHTTPCookie*cookie in [cookieJar cookies]) {
[cookieString appendFormat:@"%@=%@;",cookie.name,cookie.value];
}
//删除最后一个“;”
[cookieString deleteCharactersInRange:NSMakeRange(cookieString.length - 1, 1)];
return cookieString;
}
测试方法三:在A页面注册http(s) scheme 的情况[NSURLProtocol wk_registerScheme:@”https”]; 当然你使用的h5页面地址是以http:开头的修改为:[NSURLProtocol wk_registerScheme:@”http”];具体SURLProtocol怎么用,大家在网上搜索一下吧,后期我会写一篇关于js标签图片在iOS替换的文章有相关的介绍)。app会拦截该应用向服务器发送的所有https网络请求(包括js,cs,png等资源请求,注意:拦截的请求是WKWebView在HTTPHeaderField加入cookie前的请求,所以在canInitWithRequest函数打印NSLog(@”canInitWithRequest request.URL.absoluteString = %@,请求方式 == %@,scheme:%@,request.allHTTPHeaderFields:%@”,urlStr,request.HTTPMethod,scheme, request.allHTTPHeaderFields);得到是null,如:2018-08-23 14:16:51.390805+0800 ArtEnjoymentWeChatAuction[12030:1142813] canInitWithRequest request.URL.absoluteString = https://m.1-joy.com/market/product/cat/list.htm,请求方式 == GET,scheme:https,request.allHTTPHeaderFields:(null)),并且在fsCachedData缓存绝大部份网页数据(如何获取fsCachedData文件夹下的文件,见文章《如何在不越狱的情况下,获取app中的所有常用文件和文件夹》https://blog.csdn.net/jia12216/article/details/81536960)。当不注册http(s) scheme 的情况,fsCachedData文件夹下一般有很少的文件,几乎没有网页数据。这样做的严重后果是post 请求 body 数据被清空(这个问题我遇到过,是现在一直真实存在的问题),就是h5页面自己向后台发送的带参数的post请求,后台收到的请求参数全部没有。例如在h5页面上创建联系地址,填写成功以post的方式上传后台,那么参数全部掉丢失。当然h5页面调用iOS原生方法,由iOS使用afnet等控件发送请求发送给后台,参数不会丢失,也就是只有h5页面(WKWebView直接管理)直接向后台发送post请求才会参数丢失。这种方式能解决页面间的跳转,但是有问题。
现在我只找到这么多的方法,只有第三种不完美的方法能解决该问题。网上说的WKWebView的cookie解决方案也就上面两个类似的方案。大家抄来抄去,根本就没有实际测试过,解决不了我们的问题。为何需要从A页面(h5页面,单独的UIViewController)跳转到B页面(h5页面,单独的UIViewController),因为这样可以把两者的逻辑分离,若不分离,A页面和B页面的逻辑混在一起,就会造成逻辑过于复杂,不利于组件化。
UIWebView是和app在一个进程里,它们的数据是共享,操作app的cookie和请求就是操作UIWebView的cookie和请求。UIWebView在发送请求时都自带cookie。而WKWebView和app不在一个进程中,操作app的cookie和请求并不都能影响操作WKWebView的cookie和请求。有人说使用WKWebView的app,app的cookie有延迟,这个是客观存在的,因为它们在不同的进程中,进程中的资源是不共享的,它们不是实时同步的,是有同步时机的。当然你想它们实时同步也可以就是注册http(s) scheme。
有人说WKWebView发送请求时是不自带cookie的,这种方法是不正确的,它在WKWebView自己的进程中发送请求是自带cookie,只是它怎么自带cookie发送请求苹果系统没有向用户开放,你不知道它怎么发送的,它只提供了提供一个NSMutableURLRequest请求给WKWebView。至于你想在这个请求中自己去app的cookie给他,若你没有跳转到其他控制器,那么请求仍旧成功,只是出现本地的cookie可能出现同键不同值的键值对,若你跳转了控制器,那么你添加的cookie无效,因为你不知道WKWebView怎么自带cookie的。若真的WKWebView的请求不自带cookie ,那么我们微信授权成功进入首页,让后在首页里跳转页面不会都正常的(我们的页面除了协议页面基本都校验cookie的)。我推测可能是你新起一个WKWebView页面,也就是新起了一个WKWebView进程,这个进程首先找自己的cookie文件,若没有直接把请求的cookie设置为空了,当然它刚建立的进程,当然它的cookie文件不存在了,所以肯定被设置为空了,因此新的WKWebView页面发送出去的请求就不自带cookie了。可见由于WKWebView是多进程组件,cookie也真够混乱的。
既然页面不能完美解决两个h5页面控制器之间的cookie问题,但是咱们的普通https请求却不受影响,下面是在h5页面控制器里向后台服务器发送https请求的代码片段:
NSURL * url = [NSURL URLWithString:@"https://m.1-joy.com/market/user/weixin/subscribe.htm"];
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
NSURLSession * session = [NSURLSession sharedSession];
NSString *cookie = [self readCurrentCookieWithDomain:self.URLString];
// cookie = [NSString stringWithFormat:@"%@;PS=o8SkWwD1RV7VYdPH_PGzgC5EYdv4", cookie];
[request addValue:cookie forHTTPHeaderField:@"Cookie"];
// 发送请求
NSURLSessionTask * sessionTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
return;
}
NSString *mmmmmmm = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"mmmmmmm: %@, response:%@, error:%@, request.allHTTPHeaderFields:%@", mmmmmmm, response, error, request.allHTTPHeaderFields);
}];
[sessionTask resume];