iOS之WKWebView的坑点收录和优化处理
Posted Forever_wj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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
}
以上是关于iOS之WKWebView的坑点收录和优化处理的主要内容,如果未能解决你的问题,请参考以下文章