15-12.【HTTP】HTTP 重定向发生时,iOS URLSession 默认行为?如何自定义?

5 阅读2分钟

在 iOS 中,URLSession 对 HTTP 重定向(3xx 状态码)有非常成熟的默认处理机制。理解这一点对于处理登录跳转、短链接还原或 CDN 调度至关重要。


1. URLSession 的默认行为

当服务器返回 301302307308 状态码时,URLSession 的默认行为是:自动跟随(Auto-follow)

  • 自动跳转:它会自动读取响应头中的 Location 字段,并立即发起对新 URL 的请求。

  • 次数限制:为了防止死循环(A 跳 B,B 跳 A),系统默认最多跟随 16 次重定向,超过后请求会失败。

  • 方法改变

    • 如果收到 301302,原本的 POST 请求可能会被浏览器/系统自动降级为 GET 请求(根据 RFC 规范的历史演变)。
    • 如果收到 307308(严格重定向),请求方法会被保持不变。
  • Header 传递:默认情况下,敏感的 Header(如 Authorization)在跨域重定向时会被移除以保证安全。


2. 如何自定义重定向行为?

如果你想阻止自动跳转(例如:你想获取短链接跳转后的真实地址,但不想加载那个页面),或者你想在跳转时手动修改 Header,你需要使用 URLSessionTaskDelegate

实现 willPerformHTTPRedirection 代理方法

每当发生重定向时,URLSession 都会询问代理该如何处理。

Swift

class NetworkDelegate: NSObject, URLSessionTaskDelegate {
    
    func urlSession(_ session: URLSession, 
                    task: URLSessionTask, 
                    willPerformHTTPRedirection response: HTTPURLResponse, 
                    newRequest request: URLRequest, 
                    completionHandler: @escaping (URLRequest?) -> Void) {
        
        // 方案 A:允许默认重定向(直接返回系统准备好的 newRequest)
        // completionHandler(request)
        
        // 方案 B:拒绝重定向(返回 nil,请求会停留在当前 3xx 响应)
        // completionHandler(nil)
        
        // 方案 C:修改后重定向(例如手动添加 Token)
        var modifiedRequest = request
        modifiedRequest.setValue("Bearer new_token", forHTTPHeaderField: "Authorization")
        completionHandler(modifiedRequest)
        
        print("发现重定向至: (request.url?.absoluteString ?? "")")
    }
}

3. 不同场景的选择建议

场景推荐做法
普通网页跳转默认行为即可,无需干预。
短链接还原拦截并取消。在代理中获取 newRequest.url 后直接 completionHandler(nil)
跨域 Token 丢失自定义重定向。在代理中判断 newRequest 的 Host,如果是受信任域名,手动重新注入 Authorization
防止无限循环默认行为。系统内置的 16 次限制足以处理绝大多数情况。

4. 💡 避坑指南:Async/Await 注意事项

如果你使用最新的 URLSession.shared.data(for:) 这种 async/await 语法,你无法直接在调用处拦截重定向。你必须在初始化 URLSession 时传入自定义的 delegate

Swift

let delegate = NetworkDelegate()
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)

// 现在的请求会受 delegate 中的 willPerformHTTPRedirection 控制
let (data, response) = try await session.data(for: request)