如何在WKWebView中将HTTPS请求重定向为HTTP?请求捕获问题求助
我之前在项目里也碰到过几乎一模一样的问题——WKWebView的请求拦截逻辑对主页面的HTTPS请求生效了,但页面内的Ajax调用和后续动态请求完全没被捕获,折腾了好一阵才找到可行的方案。下面分享几个亲测有效的思路,你可以根据自己的场景选择:
1. 检查WKNavigationDelegate的拦截范围是否覆盖所有请求类型
默认情况下,WKWebView的WKNavigationDelegate里的webView(_:decidePolicyFor:decisionHandler:)方法可以捕获大部分请求,但很多人会不小心过滤掉Ajax这类子资源请求。
- 注意不要通过
navigationAction.navigationType过滤请求:Ajax请求的navigationType是.other,如果你的代码里只处理.linkActivated或.formSubmitted这类类型,自然会漏掉Ajax。 - 另外,
navigationAction.request是只读的,你没法直接修改它的scheme。所以如果要重定向,需要取消原请求,手动构造新的HTTP请求并加载。示例代码如下:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { guard let originalUrl = navigationAction.request.url, originalUrl.scheme == "https" else { decisionHandler(.allow) return } // 构造HTTP请求 var components = URLComponents(url: originalUrl, resolvingAgainstBaseURL: true) components?.scheme = "http" guard let httpUrl = components?.url else { decisionHandler(.allow) return } var httpRequest = URLRequest(url: httpUrl) httpRequest.allHTTPHeaderFields = navigationAction.request.allHTTPHeaderFields // 取消原请求,加载新的HTTP请求 decisionHandler(.cancel) webView.load(httpRequest) }
不过这种方式有个局限:对于页面内的Ajax请求,手动load可能会破坏原有的请求上下文(比如POST参数、请求头的传递),所以更可靠的方式是用下面的自定义Scheme Handler。
2. 使用WKURLSchemeHandler完全接管请求处理
如果要100%捕获所有请求(包括Ajax、静态资源、动态请求),自定义Scheme Handler是最稳妥的方案。原理是把HTTPS请求转成自定义Scheme,然后在Handler里转成HTTP请求并处理响应:
步骤1:实现自定义Scheme Handler
class CustomHTTPHandler: NSObject, WKURLSchemeHandler { func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) { guard let originalUrl = urlSchemeTask.request.url else { urlSchemeTask.didFailWithError(NSError(domain: "CustomHTTPHandler", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])) return } // 把自定义Scheme转成HTTP var components = URLComponents(url: originalUrl, resolvingAgainstBaseURL: true) components?.scheme = "http" guard let httpUrl = components?.url else { urlSchemeTask.didFailWithError(NSError(domain: "CustomHTTPHandler", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to convert URL"])) return } var httpRequest = URLRequest(url: httpUrl) httpRequest.allHTTPHeaderFields = urlSchemeTask.request.allHTTPHeaderFields // 发起网络请求 let task = URLSession.shared.dataTask(with: httpRequest) { data, response, error in if let error = error { urlSchemeTask.didFailWithError(error) return } guard let response = response as? HTTPURLResponse else { urlSchemeTask.didFailWithError(NSError(domain: "CustomHTTPHandler", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid response"])) return } // 返回响应给WebView urlSchemeTask.didReceive(response) if let data = data { urlSchemeTask.didReceive(data) } urlSchemeTask.didFinish() } task.resume() } func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) { // 可以在这里取消对应的网络请求 } }
步骤2:注册Handler并拦截HTTPS请求
在初始化WKWebView的时候,注册自定义Scheme,并在NavigationDelegate里把HTTPS请求转成自定义Scheme:
let configuration = WKWebViewConfiguration() // 注册自定义Scheme,比如"myhttp" configuration.setURLSchemeHandler(CustomHTTPHandler(), forURLScheme: "myhttp") let webView = WKWebView(frame: view.bounds, configuration: configuration) webView.navigationDelegate = self // 在NavigationDelegate里处理重定向 func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { guard let originalUrl = navigationAction.request.url, originalUrl.scheme == "https" else { decisionHandler(.allow) return } // 转成自定义Scheme var components = URLComponents(url: originalUrl, resolvingAgainstBaseURL: true) components?.scheme = "myhttp" guard let customUrl = components?.url else { decisionHandler(.allow) return } var customRequest = URLRequest(url: customUrl) customRequest.allHTTPHeaderFields = navigationAction.request.allHTTPHeaderFields decisionHandler(.cancel) webView.load(customRequest) }
这种方式能完全控制所有请求的生命周期,不管是主页面还是Ajax都能捕获处理。
3. 注入JavaScript修改页面内的请求逻辑
如果不想在Native层做复杂的拦截,也可以通过注入JS直接修改页面内的XMLHttpRequest和fetch方法,把所有HTTPS请求自动转成HTTP。这种方式对页面内的动态请求(比如Ajax、懒加载资源)特别有效:
步骤1:编写JS注入脚本
(function() { // 拦截XMLHttpRequest const originalXhrOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url) { // 替换HTTPS为HTTP if (url.startsWith('https://')) { url = url.replace('https://', 'http://'); } return originalXhrOpen.apply(this, arguments); }; // 拦截fetch请求 const originalFetch = window.fetch; window.fetch = function(resource, options) { if (typeof resource === 'string') { if (resource.startsWith('https://')) { resource = resource.replace('https://', 'http://'); } } else if (resource instanceof Request) { resource = new Request( resource.url.replace('https://', 'http://'), resource ); } return originalFetch(resource, options); }; })();
步骤2:注入到WebView中
let jsCode = """ (function() { const originalXhrOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url) { if (url.startsWith('https://')) { url = url.replace('https://', 'http://'); } return originalXhrOpen.apply(this, arguments); }; const originalFetch = window.fetch; window.fetch = function(resource, options) { if (typeof resource === 'string') { if (resource.startsWith('https://')) { resource = resource.replace('https://', 'http://'); } } else if (resource instanceof Request) { resource = new Request( resource.url.replace('https://', 'http://'), resource ); } return originalFetch(resource, options); }; })(); """ let userScript = WKUserScript( source: jsCode, injectionTime: .atDocumentStart, // 在页面加载前注入,确保所有请求都被拦截 forMainFrameOnly: false // 包括iframe内的请求 ) let configuration = WKWebViewConfiguration() configuration.userContentController.addUserScript(userScript) let webView = WKWebView(frame: view.bounds, configuration: configuration)
这种方式简单直接,不需要处理Native层的复杂逻辑,适合大部分场景。
4. 检查ATS和WebView配置的限制
最后别忘了检查两个基础配置:
- App Transport Security (ATS):因为要加载HTTP请求,需要在
Info.plist中配置NSAppTransportSecurity,要么设置NSAllowsArbitraryLoads为YES,要么针对特定域名添加NSExceptionDomains。 - WebView的域名限制:如果你的
WKWebViewConfiguration设置了limitsNavigationsToAppBoundDomains = true,只有绑定的域名才能被加载,可能会导致重定向后的HTTP请求被拦截,所以如果不需要这个限制,设置为false即可。
内容的提问来源于stack exchange,提问作者jailani

