WWDC Quick Look 💓 By SwiftGGTeam
Analyze HTTP traffic in Instruments

Analyze HTTP traffic in Instruments

观看原视频

Highlight

Instruments 13 在 Network 模板中加入 HTTP Traffic Instrument,让开发者按进程、URLSession、URLSessionTask 和 HTTP transaction 查看 App 的 HTTP 流量,用同一份 trace 定位性能问题、缓存错误、认证问题和隐私风险。

核心内容

一个 App 的网络问题,经常不会直接出现在代码里。

图片加载慢,代码里只看到一组 URLSessionTask。用户反复登录,代码里只看到服务器返回了 401。收藏列表没有更新,界面看起来像状态管理错误。第三方 SDK 偷发数据,源码甚至不在你的工程里。

以前排查这些问题,需要在客户端日志、服务器日志、代理抓包工具之间来回切换。每一种工具只能看到一部分事实。你知道请求发出去了,却不一定知道它属于哪个 URLSession。你看到 HTTP header,却不一定知道是哪一行代码调用了 resume()

Apple 在 Instruments 13 的 Network 模板里加入了 HTTP Traffic Instrument。它直接接入 Apple Networking stack(Apple 网络栈),记录经过 URL Loading System(URL 加载系统)的 HTTP 流量。HTTP/3、VPN 流量、磁盘缓存命中、网络错误,都会在同一条时间线上出现。

这场演讲用一个狗狗图片 App 做演示。首页加载慢,是服务器只支持 HTTP/1.1,导致同一连接上的请求排队。登录状态丢失,是服务器发回了过期 cookie。收藏列表不刷新,是请求直接命中了本地缓存。第三方登录 SDK 在用户点击前就上传位置坐标,是隐私问题。

关键变化是,网络请求不再只是 URL 和状态码。Instruments 把它放回开发者熟悉的对象模型里:进程、URLSessionURLSessionTask、transaction(一次 HTTP 请求和响应)。你可以从时间线看到问题,也可以从 backtrace(回溯)追到源码。

详细内容

1. 用对象层级阅读 HTTP 流量

02:04)HTTP Traffic Instrument 的顶层轨道显示 trace 中正在运行的 URLSessionTask 数量。下一层按进程拆分,再下一层是进程使用的 URLSession。最底层按请求域名拆分,并显示 task 和 transaction 的状态。

如果工程里有多个 session,默认名字很难和代码对应。Apple 建议给 session 和 task 加语义名称。Instruments 会用这些名称标注时间线。

import Foundation

final class FeedLoader {
    private let session: URLSession

    init() {
        let configuration = URLSessionConfiguration.default
        session = URLSession(configuration: configuration)
        session.sessionDescription = "DogFeed Image Loader"
    }

    func loadThumbnail(from url: URL) {
        let task = session.dataTask(with: url) { data, response, error in
            if let error {
                print("thumbnail failed: \(error)")
                return
            }

            print("thumbnail bytes: \(data?.count ?? 0)")
        }

        task.taskDescription = "Load latest dog thumbnail"
        task.resume()
    }
}

关键点:

  • URLSessionConfiguration.default 创建默认 URLSession 配置。
  • URLSession(configuration:) 创建一个独立 session,它会出现在 Instruments 的 session 层级里。
  • session.sessionDescription 给 session 加名字,方便把时间线和代码对象对应起来。
  • session.dataTask(with:) 创建一个 URLSessionTask
  • task.taskDescription 给 task 加名字,Instruments 会用它标注 task interval(任务区间)。
  • task.resume() 启动请求,演讲中说明 task interval 从这里开始,直到 completion block 调用前结束。

2. 用 transaction 状态定位 HTTP/1 队头阻塞

13:18)演示 App 首页需要先请求图片列表,再并发请求每张缩略图。第一次 trace 显示,初始屏幕加载超过 7 秒。后启动的缩略图 task 变慢,紫色 Blocked 状态越来越长。

切到 HTTP Transactions by Connection 视图后,问题变清楚了。每个 transaction 都要等同一连接上的前一个 transaction 完成,才能发送自己的请求。这是 HTTP/1 的 Head of Line Blocking(队头阻塞)。服务器启用 HTTP/2 后,请求可以在一个连接上多路复用,总耗时降到 3 秒以内。

Instruments
→ Profile with Network template
→ HTTP Traffic
→ Select app process
→ Select server domain
→ Track display: HTTP Transactions by Connection
→ Look for: increasing Blocked state on the same connection
→ Check label: HTTP version

关键点:

  • Network template 同时包含 Network Connections instrument 和 HTTP Traffic instrument。
  • HTTP Traffic 顶层轨道用来定位流量高峰。
  • Select server domain 可以只看某个后端域名的请求。
  • HTTP Transactions by Connection 会按连接分组 transaction,用来判断请求是否排在同一连接上。
  • increasing Blocked state 是本场演示中定位队头阻塞的直接信号。
  • HTTP version 会显示在 transaction label 上,用来确认请求走的是 HTTP/1.1、HTTP/2 或其他版本。

18:37)用户第一次收藏图片时,服务器返回 401。用户输入账号密码后,URL Loading System 重试 transaction,并收到 201。两个 transaction 属于同一个 task,因为这是同一次认证挑战和重试流程。

第二次收藏图片时,App 又要求登录。Instruments 显示请求再次收到 401,请求 label 上没有 Cookie 图标。回看上一次成功登录的 response,服务器确实发了 Set-Cookie。展开 transaction detail 后发现,cookie 的过期时间是 2020 年 3 月,已经过期,所以 URLSession 后续不会发送它。

HTTP/1.1 201 Created
Set-Cookie: session=abc123; Expires=Wed, 18 Mar 2020 12:00:00 GMT; Path=/; Secure; HttpOnly
Content-Type: application/json

关键点:

  • 201 Created 对应演示中第一次登录后重试成功的状态码。
  • Set-Cookie 表示服务器尝试设置登录 cookie。
  • Expires=Wed, 18 Mar 2020 12:00:00 GMT 是问题根源,演讲中指出这个日期已经在过去。
  • Path=/ 限制 cookie 的路径范围。
  • Secure 表示 cookie 只通过安全连接发送。
  • HttpOnly 表示客户端脚本不能读取该 cookie。
  • 过期 cookie 不会被 URLSession 发送,所以下一次收藏请求仍然没有 Cookie header。

4. 用缓存策略修复过期列表

24:40)收藏列表没有显示新收藏的图片。Instruments 里能找到加载收藏列表的 task,但它只用了几毫秒。切到 HTTP Transactions by Connection 后发现,这个 transaction 没有跑在网络连接上,它跑在 Local Cache 上。

这说明请求没有发给服务器。演讲中的修复方式是在 URLRequest 上设置 reloadRevalidatingCacheData。这样客户端会向服务器确认缓存是否仍然有效。服务器可以返回 304,让客户端继续使用本地缓存;如果数据变化,服务器返回新数据。

import Foundation

func loadFavorites(from endpoint: URL, session: URLSession = .shared) {
    var request = URLRequest(url: endpoint)
    request.cachePolicy = .reloadRevalidatingCacheData

    let task = session.dataTask(with: request) { data, response, error in
        if let error {
            print("favorites failed: \(error)")
            return
        }

        if let httpResponse = response as? HTTPURLResponse {
            print("status: \(httpResponse.statusCode)")
        }

        print("favorites bytes: \(data?.count ?? 0)")
    }

    task.taskDescription = "Reload favorites with cache revalidation"
    task.resume()
}

关键点:

  • URLRequest(url:) 创建要加载收藏列表的请求。
  • request.cachePolicy 控制这一次请求如何使用本地缓存。
  • .reloadRevalidatingCacheData 是演讲中使用的缓存策略,会向服务器确认缓存有效性。
  • session.dataTask(with: request) 使用带缓存策略的 request 创建 task。
  • HTTPURLResponse.statusCode 可以打印服务器返回的状态码;演讲提到服务器可用 304 表示缓存仍有效。
  • task.taskDescription 让 Instruments 中的 task 更容易识别。
  • task.resume() 发起请求。

5. 用 backtrace 和 HAR 导出审计第三方 SDK

29:19)演讲最后检查一个第三方登录 SDK。App 刚进入登录页,还没有点击 SDK 的登录按钮,Instruments 已经显示 Pets Sign On Network session 在发送请求。请求是 POST,backtrace 显示它来自 SDK,并且牵涉到 CoreLocation。

展开 transaction detail 后,请求 body 里包含位置坐标。演讲明确指出,这种行为会侵犯用户隐私。开发者可以保存 trace,然后用 xctrace 导出 HTTP Archive(HAR)文件,发给 SDK 团队复现问题。

xcrun xctrace export \
  --input ~/Desktop/PrivacyViolation.trace \
  --har \
  --output ~/Desktop/PrivacyViolation.har

关键点:

  • xcrun 调用 Xcode 附带的命令行工具。
  • xctrace export 导出 Instruments trace 中的数据。
  • --input ~/Desktop/PrivacyViolation.trace 指定演讲中保存的 trace 文件。
  • --har 选择 HTTP Archive 格式,用于交换 HTTP 流量信息。
  • HAR 是 JSON-based format(基于 JSON 的格式),可以用支持 HAR 的工具、文本编辑器或脚本检查。
  • 演讲说明 HAR 不包含 Instruments 专有信息,例如 URLSession 和 backtrace,但足够让对方调查 HTTP 内容。

核心启发

1. 给网络层加可观测名称

  • 做什么:为每个业务场景的 URLSession 和关键 URLSessionTask 设置描述,例如图片加载、登录、收藏同步。
  • 为什么值得做:HTTP Traffic Instrument 会把 sessionDescriptiontaskDescription 显示在时间线上,排查问题时不用靠 URL 猜业务含义。
  • 怎么开始:在网络封装初始化 URLSession 后设置 session.sessionDescription,在创建 task 后设置 task.taskDescription,再用 Network 模板录制一次核心流程。

2. 建一个首屏网络性能检查流程

  • 做什么:每次改首页接口或图片加载策略后,用 Instruments 检查首屏请求是否出现长时间 Blocked 状态。
  • 为什么值得做:演讲中的首页从超过 7 秒降到 3 秒以内,核心线索就是 HTTP Transactions by Connection 里的队头阻塞。
  • 怎么开始:录制 App 启动到首屏完成的 trace,按域名展开 HTTP Traffic,切到 HTTP Transactions by Connection,检查 HTTP version 和 Blocked 状态。
  • 做什么:把登录、重试、再次请求受保护资源这条链路录成 trace,确认服务器是否设置了有效 cookie,后续请求是否带上 Cookie header。
  • 为什么值得做:演讲中的 bug 不在客户端保存逻辑,而在服务器返回了过期 Set-Cookie
  • 怎么开始:在 transaction detail 中查看 response headers 的 Set-Cookie,再查看下一次 request label 或 request headers 是否包含 Cookie。

4. 给会变化的列表加缓存复核

  • 做什么:对收藏列表、通知列表、协作数据这类会变化的数据,按需使用缓存复核策略。
  • 为什么值得做:演讲中的收藏列表命中 Local Cache,导致用户看不到新收藏。reloadRevalidatingCacheData 可以让客户端向服务器确认缓存是否过期。
  • 怎么开始:找到对应 URLRequest,设置 request.cachePolicy = .reloadRevalidatingCacheData,再用 Instruments 确认 transaction 是否走到连接或收到 304。

5. 把第三方 SDK 接入前的隐私检查自动化

  • 做什么:集成 SDK 后,录制 App 冷启动、进入登录页、点击 SDK 按钮前后的 HTTP 流量,检查是否有未预期的请求体、位置数据或认证信息。
  • 为什么值得做:HTTP Traffic Instrument 能显示 SDK 自己创建的 session、POST 请求、headers、body 和 backtrace。
  • 怎么开始:用 Network 模板录制 trace,选择 SDK session,查看 transactions detail;发现问题后用 xcrun xctrace export --har 导出 HAR 附到 bug report。

关联 Session

评论

GitHub Issues · utterances