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

如何通过编程监控App中URLSession的所有网络请求?

编程监控App中URLSession的所有HTTP请求

要实现全局监控App内(包括第三方库)所有URLSession发起的HTTP/HTTPS请求,获取请求URL、头、体及响应数据,核心方案是利用自定义URLProtocol拦截请求,再通过Method Swizzling确保所有Session配置都加载该Protocol。

实现步骤

1. 自定义URLProtocol子类

URLProtocol是系统提供的请求拦截机制,通过重写其方法可以捕获、修改、转发请求,并收集请求/响应数据。

import Foundation

class HTTPMonitorProtocol: URLProtocol {
    private var task: URLSessionTask?
    
    // 判断是否拦截当前请求
    override class func canInit(with request: URLRequest) -> Bool {
        // 仅拦截HTTP/HTTPS请求,且避免重复拦截已处理的请求
        guard let scheme = request.url?.scheme,
              (scheme.lowercased() == "http" || scheme.lowercased() == "https"),
              URLProtocol.property(forKey: "isMonitored", in: request) == nil else {
            return false
        }
        return true
    }
    
    // 返回标准化请求(通常直接返回原请求)
    override class func canonicalRequest(for request: URLRequest) -> URLRequest {
        return request
    }
    
    // 开始处理请求:收集请求数据,转发请求
    override func startLoading() {
        var mutableRequest = request
        // 标记请求已被监控,防止重复拦截
        URLProtocol.setProperty(true, forKey: "isMonitored", in: mutableRequest)
        
        // 收集请求核心信息
        print("=== 请求详情 ===")
        print("URL: \(mutableRequest.url?.absoluteString ?? "未知")")
        print("方法: \(mutableRequest.httpMethod ?? "未知")")
        print("请求头: \(mutableRequest.allHTTPHeaderFields ?? [:])")
        if let bodyData = mutableRequest.httpBody, let bodyStr = String(data: bodyData, encoding: .utf8) {
            print("请求体: \(bodyStr)")
        }
        
        // 创建Session转发请求,自身作为代理处理响应
        let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
        task = session.dataTask(with: mutableRequest)
        task?.resume()
    }
    
    // 停止加载时清理资源
    override func stopLoading() {
        task?.cancel()
        task = nil
    }
}

// 扩展实现URLSession代理,收集响应数据
extension HTTPMonitorProtocol: URLSessionDataDelegate {
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
        // 收集响应信息
        print("=== 响应详情 ===")
        if let httpResponse = response as? HTTPURLResponse {
            print("状态码: \(httpResponse.statusCode)")
            print("响应头: \(httpResponse.allHeaderFields)")
        }
        // 将响应传递给原请求的客户端,保证业务逻辑正常执行
        client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
        completionHandler(.allow)
    }
    
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        // 收集返回数据
        if let dataStr = String(data: data, encoding: .utf8) {
            print("返回数据: \(dataStr)")
        }
        // 将数据传递给原请求的客户端
        client?.urlProtocol(self, didLoad: data)
    }
    
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        // 收集错误信息
        if let error = error {
            print("请求失败: \(error.localizedDescription)")
            client?.urlProtocol(self, didFailWithError: error)
        } else {
            client?.urlProtocolDidFinishLoading(self)
        }
        // 销毁Session,避免内存泄漏
        session.finishTasksAndInvalidate()
    }
}

2. 全局注册并Hook Session配置

仅注册URLProtocol无法覆盖第三方库自定义的URLSessionConfiguration,需通过Method Swizzling修改所有配置的protocolClasses,确保自定义Protocol被加入。

import Foundation

extension URLSessionConfiguration {
    static func setupHTTPMonitoring() {
        // 注册自定义Protocol
        URLProtocol.registerClass(HTTPMonitorProtocol.self)
        // Swizzle protocolClasses getter,自动添加自定义Protocol
        swizzleProtocolClasses()
    }
    
    private static func swizzleProtocolClasses() {
        let originalSelector = #selector(getter: protocolClasses)
        let swizzledSelector = #selector(getter: swizzled_protocolClasses)
        
        guard let originalMethod = class_getInstanceMethod(self, originalSelector),
              let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else {
            return
        }
        
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
    
    @objc private var swizzled_protocolClasses: [AnyClass]? {
        // 调用原方法获取默认的Protocol列表
        let originalClasses = swizzled_protocolClasses
        var modifiedClasses = originalClasses ?? []
        // 避免重复添加自定义Protocol
        if !modifiedClasses.contains(HTTPMonitorProtocol.self) {
            modifiedClasses.insert(HTTPMonitorProtocol.self, at: 0)
        }
        return modifiedClasses
    }
}

3. 在App启动时初始化监控

在AppDelegate或SceneDelegate的启动方法中调用初始化代码:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // 开启HTTP请求监控(可通过开关控制生产环境是否启用)
    #if DEBUG
    URLSessionConfiguration.setupHTTPMonitoring()
    #endif
    // 或者用远程配置控制:
    // if RemoteConfig.shared.isHTTPMonitoringEnabled {
    //     URLSessionConfiguration.setupHTTPMonitoring()
    // }
    return true
}

生产环境注意事项

  • 开关控制:务必通过编译宏、远程配置等方式控制监控的开启/关闭,避免影响线上性能。
  • 性能优化:请求/响应数据的收集、存储或上报需放在异步线程,避免阻塞主线程;避免在监控逻辑中做耗时操作。
  • 特殊场景处理:若请求体使用httpBodyStream,需额外读取流数据;若App有证书校验逻辑,需在自定义Protocol中同步实现。
  • 内存泄漏:确保stopLoading方法中清理Task和Session资源,避免内存泄漏。

内容的提问来源于stack exchange,提问作者Silver Ringvee

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 02:55:23