You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在WKWebView中将HTTPS请求重定向为HTTP?请求捕获问题求助

解决WKWebView中HTTPS转HTTP时Ajax及后续请求未被捕获的问题

我之前在项目里也碰到过几乎一模一样的问题——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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:20:07