iOS之深入解析WKWebView的坑点收录和优化处理

一、Cookie 处理

① Cookie 说明

  • WKWebView 在设置 Cookie 的时候,经常做的是在请求的请求头里添加 Cookie,但这只是把 Cookie 发送给了服务端,本地并没有保存 Cookie,Cookie 最终要写到 WebView 的一个 Cookie 文件目录里面,后续 WebView 里面自己的发起的请求或者跳转才能在发起请求的时候,在对应的域名下面取到 Cookie 传出去。
  • Webview 加载 H5 页面,实际上是把页面相关的 .html、js、css 文件下载到本地,然后再加载,这时页面去获取 Cookie 的时候,是去本地 WebView 里的 Cookie 文件目录里查找,如果没有设置的话肯定就获取不到,所以在设置 Cookie 的时候,服务端和客户端都要设置。
② 服务端 Cookie 设置
  • 在使用 UIWebView 的时候,是通过 NSHTTPCookieStorage 来管理 Cookie 的,如下,给 baidu.tech 这个域名添加一个名为 user 的 Cookie:
	var props = Dictionary<HTTPCookiePropertyKey, Any>()
	props[HTTPCookiePropertyKey.name] = "user"
	props[HTTPCookiePropertyKey.value] = "admin"
	props[HTTPCookiePropertyKey.path] = "/"
	props[HTTPCookiePropertyKey.domain] = "baidu.tech"
	props[HTTPCookiePropertyKey.version] = "0"
	props[HTTPCookiePropertyKey.originURL] = "baidu.tech"
	if let cookie = HTTPCookie(properties: props) {
	    HTTPCookieStorage.shared.setCookie(cookie)
	}
  • WKWebView Cookie 问题在于 WKWebView 发起的请求不会自动带上存储于 NSHTTPCookieStorage 容器中的 Cookie。解决办法也很简单,就是在 WKWebView 发起请求之前,先从 NSHTTPCookieStorage 读取 Cookie,然后手动往 URLRequest 的请求头里添加一下 Cookie:
	func getCookie() -> String {
	    var cookieString = ""
		if let cookies = HTTPCookieStorage.shared.cookies {
			for cookie in cookies {
			if cookie.domain == cookieDomain {
				let str = "\(cookie.name)=\(cookie.value)"
				cookieString.append("\(str);")
			}
		}
		return cookieString
	}
	
	var request = URLRequest(url: URL(string: "https://baidu.tech"))
	request.addValue(getCookie(), forHTTPHeaderField: "Cookie")
  • 当服务器页面发生重定向的时候,此时第一次在 RequestHeader 中写入的 Cookie 会丢失,还需要对重定向的请求重新做添加 Cookie 的处理。

② 客户端 Cookie 设置

  • 当页面加载的时候,后端无论是啥语言,都能从请求头里看到 Cookie 了,但是后端渲染返回页面后,在客户端的 WebView 里运行的时候,JS 在执行的时候调用 document.cookie API 是读取不到 Cookie 的,所以还得针对客户端 Cookie 进行处理:
	var cookieString = ""
	if let cookies = HTTPCookieStorage.shared.cookies {
		for cookie in cookies {
			if cookie.domain == "baidu.tech" {
				let str = "\(cookie.name)=\(cookie.value)"
				cookieString.append("document.cookie='\(str);path=/;domain=baidu.tech';")
			}
		}
	}
	let cookieScript = WKUserScript(source: cookieString, injectionTime: .atDocumentStart, forMainFrameOnly: false)
	let userContentController = WKUserContentController()
	userContentController.addUserScript(cookieScript)
	
	let webViewConfig = WKWebViewConfiguration()
	webViewConfig.userContentController = userContentController
	
	let webV = WKWebView(frame: CGRect.zero, configuration: webViewConfig)
  • 客户端 Cookie 注入实际上就是创建一个 JS 脚本,让 WebView 去执行,推荐在 .atDocumentStart 这个时机进行预置静态 JS 的注入,这样 WebView 在加载后端返回的静态页面的时候,就可以拿到保存着客户端的 Cookie 了。

二、URL 拦截

① Web 页面重定向问题

  • 在 WKWebView 中,每一次页面跳转之前,都会调用下面的回调函数:
	func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)
  • 重定向问题有两种:
    • 服务器页面重定向,需要对新发起的请求重新设置 Cookie;
    • 本地页面重定向,只要客户端设置了 Cookie,那么就不需要再处理。
  • 因此,如果是服务器页面重定向,那么判断此时 Request 是否有需要的 Cookie,没有就 Cancel 掉,修改 Request 重新发起。
	func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)
	{
	    var shouldCancelLoadURL = false
	    if let cookie = navigationAction.request.value(forHTTPHeaderField: "Cookie") {
	        if cookie.contains("user") {
	            shouldCancelLoadURL = false
	        } else {
	            var request = URLRequest(url: URL(string: (navigationAction.request.url?.absoluteString)!)!)
	            request.addValue(getCookie(), forHTTPHeaderField: "Cookie")
	            webView.load(request)
	            shouldCancelLoadURL = true
	        }
	    } else {
	        var request = URLRequest(url: URL(string: (navigationAction.request.url?.absoluteString)!)!)
	        request.addValue(getCookie(), forHTTPHeaderField: "Cookie")
			webView.load(request)
			shouldCancelLoadURL = true
	    }
	    
	    if shouldCancelLoadURL {
	    	decisionHandler(WKNavigationActionPolicy.cancel)
		} else {
			decisionHandler(WKNavigationActionPolicy.allow)
		}
	}

② 跨域问题

  • 针对跨域的问题,解决办法和上面的方法类似,仅仅是判断条件不同:
	func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)
	{
	    var shouldCancelLoadURL = false
	    if let url = navigationAction.request.url?.absoluteString {
	        if url.contains("baidu.tech") { // 原来的域名
	            shouldCancelLoadURL = false
	        } else {
	        	// 重新发起请求,种Cookie
	            shouldCancelLoadURL = true
	        }
	    } else {
	    	// 重新发起请求,种Cookie
			shouldCancelLoadURL = true
	    }
	    
	    if shouldCancelLoadURL {
	    	decisionHandler(WKNavigationActionPolicy.cancel)
		} else {
			decisionHandler(WKNavigationActionPolicy.allow)
		}
	}

③ 非法跳转请求拦截

  • 非法跳转请求拦截就是由网页发出一条新的跳转请求,跳转的目的地是一个非法的压根就不存在的地址,比如:
	// 常规的Http地址
	https://baidu.com/xxxx?xx=xx
	// 非法请求通信地址
	dwdwdw://yangdw/action?param=paramobj
  • url 地址组成如下:
    • 协议:也就是 http/https/file 等,非法请求通信地址用了 dwdwdw;
    • 域名:上面的 .baidu.com 或 yangdw;
    • 路径:上面的 xxxx? 或 action?;
    • 参数:上面的 xx=xx 或 param=paramobj;
  • 如果构建一条假 url:
    • 用协议与域名当做通信识别;
    • 用路径当做指令识别;
    • 用参数当做数据传递;
  • 客户端会无差别拦截所有请求,真正的 url 地址应该照常放过,只有协议域名匹配的 url 地址才应该被客户端拦截,拦截下来的 url 不会导致 WebView 继续跳转错误地址,因此无感知,相反拦截下来的 url 可以读取其中路径当做指令,读取其中参数当做数据,从而根据约定调用对应的 Native 原生代码。
  • 以上其实是一种协议约定,只要 JS 按着这个约定协议生成假 url,Native 按着约定协议拦截/读取假 url,整个流程就能跑通。

三、User-Agent 设置

① 全局设置

  • App 内所有 Web 请求的 User-Agent 全部被修改:
	// UIWebView
	let webView = UIWebView(frame: CGRect.zero)
	let userAgent = webView.stringByEvaluatingJavaScript(from: "navigator.userAgent")
	if let agent = userAgent {
	    let user = "@\(agent);extra_user_agent"
	    let dict = ["UserAgent":user]
	    UserDefaults.standard.register(defaults: dict)
	}
	
	// WKWebView
	let webV = WKWebView(frame: CGRect.zero)
	webV.evaluateJavaScript("navigator.userAgent") { (result, error) in
		if let oldAgent = result as? String {
			let user = "@\(oldAgent);extra_user_agent"
			let dict = ["UserAgent":user]
			UserDefaults.standard.register(defaults: dict)
		}
	}

② 单个 WebView 设置

  • 在 iOS9,WKWebView 提供了一个非常便捷的属性去更改 User-Agent,就是 customUserAgent 属性,这样使用起来不仅方便,也不会全局更改 User-Agent,可惜的是 iOS9 才有,如果适配 iOS8,还是需要使用上面的方法。
	let webView = UIWebView(frame: CGRect.zero)
	let userAgent = webView.stringByEvaluatingJavaScript(from: "navigator.userAgent")
	if let agent = userAgent {
	    let user = "@\(agent);extra_user_agent"
	    webView.customUserAgent = user
	}
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

╰つ栺尖篴夢ゞ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值