agent-browser 源码分析(五):Network 拦截与路由
Network 拦截与路由:Fetch Domain 实战
本文是「agent-browser 代码原理」系列第 5 篇,深入剖析 agent-browser 的 Network 拦截和路由功能。
一、为什么需要 Network 拦截
浏览器自动化中,Network 拦截有三大应用场景:
- Mock API 响应:测试前端逻辑时,不需要真实后端
- 修改请求头:添加认证 Token、User-Agent 等
- 拦截广告/追踪:屏蔽不必要的请求,加速页面加载
agent-browser 通过 CDP 的 Fetch domain 实现这些功能。
二、CDP Fetch Domain 基础
2.1 启用拦截
client.send_command(
"Fetch.enable",
Some(json!({
"patterns": [
{ "urlPattern": "*" } // 拦截所有 URL
]
})),
Some(session_id),
).await?;
2.2 请求暂停事件
当 Chrome 匹配到拦截规则时,会发送 Fetch.requestPaused 事件:
{
"method": "Fetch.requestPaused",
"params": {
"requestId": "interception-job-1.0",
"request": {
"url": "https://api.example.com/data",
"method": "GET",
"headers": {...}
},
"resourceType": "XHR"
}
}
2.3 三种处理方式
| 方式 | CDP 命令 | 用途 |
|---|---|---|
| 继续请求 | Fetch.continueRequest |
放行,可修改请求头 |
| Mock 响应 | Fetch.fulfillRequest |
返回自定义响应 |
| 中断请求 | Fetch.failRequest |
模拟网络错误 |
三、agent-browser 的路由系统
3.1 RouteEntry 结构
pub struct RouteEntry {
pub url_pattern: String, // URL 匹配模式
pub response: Option<RouteResponse>, // Mock 响应
pub abort: bool, // 是否中断
pub resource_types: Vec<String>, // 资源类型过滤
}
pub struct RouteResponse {
pub status: Option<u16>, // HTTP 状态码
pub body: Option<String>, // 响应体
pub content_type: Option<String>, // Content-Type
pub headers: Option<HashMap<String, String>>, // 响应头
}
3.2 用户接口
# Mock API 响应
agent-browser route "**/api/users" --mock '{"status": 200, "body": "{\"users\":[]}"}'
# 中断请求
agent-browser route "**/*.png" --abort
# 修改请求头
agent-browser open example.com --headers '{"Authorization": "Bearer token123"}'
四、拦截处理器实现
4.1 启动拦截
当用户首次使用 --headers 或 route 时,agent-browser 自动启用 Fetch domain:
// actions.rs:handle_navigate
let scoped_headers = cmd.get("headers")
.and_then(|v| v.as_object())
.filter(|m| !m.is_empty());
if let Some(headers_map) = scoped_headers {
if let Some(origin) = url::Url::parse(url)
.ok()
.map(|u| u.origin().ascii_serialization())
{
let headers: HashMap<String, String> = headers_map
.iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
.collect();
let mut map = state.origin_headers.write().await;
let first = map.is_empty();
map.insert(origin, headers);
// 首次使用时启用 Fetch 拦截
if first {
let session_id = mgr.active_session_id()?.to_string();
let mut params = json!({ "patterns": [{ "urlPattern": "*" }] });
mgr.client.send_command("Fetch.enable", Some(params), Some(&session_id)).await?;
}
}
}
4.2 事件处理循环
在 BrowserManager 启动时,会创建一个后台任务持续监听 Fetch.requestPaused 事件:
// browser.rs 伪代码
async fn fetch_handler_task(
client: CdpClient,
routes: Arc<RwLock<Vec<RouteEntry>>>,
origin_headers: Arc<RwLock<HashMap<String, HashMap<String, String>>>>,
) {
let mut events = client.subscribe();
while let Ok(event) = events.recv().await {
if event.method != "Fetch.requestPaused" {
continue;
}
let request_id = event.params.get("requestId").and_then(|v| v.as_str())
.unwrap_or("");
let request = event.params.get("request").unwrap_or(&Value::Null);
let url = request.get("url").and_then(|v| v.as_str()).unwrap_or("");
let resource_type = event.params.get("resourceType")
.and_then(|v| v.as_str()).unwrap_or("");
// 检查路由规则
let routes = routes.read().await;
for route in routes.iter() {
if match_url_pattern(&route.url_pattern, url) {
if route.abort {
// 中断请求
let _ = client.send_command(
"Fetch.failRequest",
Some(json!({
"requestId": request_id,
"errorReason": "Aborted"
})),
None,
).await;
} else if let Some(ref response) = route.response {
// Mock 响应
let _ = client.send_command(
"Fetch.fulfillRequest",
Some(json!({
"requestId": request_id,
"responseCode": response.status.unwrap_or(200),
"body": base64_encode(response.body.as_deref().unwrap_or("")),
})),
None,
).await;
}
return;
}
}
// 检查是否需要注入 headers
let origin_headers = origin_headers.read().await;
if let Some(headers) = origin_headers.get(&get_origin(url)) {
let mut merged_headers = Vec::new();
for (key, value) in headers {
merged_headers.push(json!({"name": key, "value": value}));
}
let _ = client.send_command(
"Fetch.continueRequest",
Some(json!({
"requestId": request_id,
"headers": merged_headers
})),
None,
).await;
return;
}
// 默认放行
let _ = client.send_command(
"Fetch.continueRequest",
Some(json!({"requestId": request_id})),
None,
).await;
}
}
4.3 URL 模式匹配
agent-browser 支持 glob 风格的 URL 模式:
fn match_url_pattern(pattern: &str, url: &str) -> bool {
// ** 匹配任意路径段
// * 匹配单个路径段
// ? 匹配单个字符
let regex = pattern
.replace("**", ".*")
.replace("*", "[^/]*")
.replace("?", ".");
Regex::new(®ex).map(|re| re.is_match(url)).unwrap_or(false)
}
示例:
| 模式 | 匹配的 URL |
|---|---|
**/api/** |
https://example.com/api/users |
*.png |
https://example.com/image.png |
https://api.example.com/** |
https://api.example.com/v1/data |
五、Network 事件追踪
除了拦截,agent-browser 还记录了完整的网络请求/响应信息,用于导出 HAR 文件。
5.1 HarEntry 结构
pub struct HarEntry {
pub request_id: String,
pub wall_time: f64, // Unix 时间戳(秒)
pub method: String,
pub url: String,
pub request_headers: Vec<(String, String)>,
pub post_data: Option<String>,
pub request_body_size: i64,
pub resource_type: String,
pub status: Option<i64>,
pub status_text: String,
pub http_version: String,
pub response_headers: Vec<(String, String)>,
pub mime_type: String,
pub response_body_size: i64,
}
5.2 事件监听
async fn network_event_task(client: CdpClient, tracker: NetworkTracker) {
let mut events = client.subscribe();
while let Ok(event) = events.recv().await {
match event.method.as_str() {
"Network.requestWillBeSent" => {
let request_id = event.params["requestId"].as_str().unwrap_or("");
let request = &event.params["request"];
tracker.record_request(HarEntry {
request_id: request_id.to_string(),
method: request["method"].as_str().unwrap_or("GET").to_string(),
url: request["url"].as_str().unwrap_or("").to_string(),
// ...
}).await;
}
"Network.responseReceived" => {
let request_id = event.params["requestId"].as_str().unwrap_or("");
let response = &event.params["response"];
tracker.update_response(request_id, ResponseInfo {
status: response["status"].as_i64(),
status_text: response["statusText"].as_str().unwrap_or("").to_string(),
// ...
}).await;
}
"Network.loadingFinished" => {
let request_id = event.params["requestId"].as_str().unwrap_or("");
let encoded_len = event.params["encodedDataLength"].as_i64().unwrap_or(0);
tracker.update_body_size(request_id, encoded_len).await;
}
_ => {}
}
}
}
5.3 HAR 导出
agent-browser open example.com
agent-browser har --output trace.har
六、代理认证
当使用需要认证的 HTTP 代理时,agent-browser 自动处理 Fetch.authRequired 事件:
"Fetch.authRequired" => {
let request_id = event.params["requestId"].as_str().unwrap_or("");
if let Some(creds) = proxy_credentials.read().await.as_ref() {
let _ = client.send_command(
"Fetch.continueWithAuth",
Some(json!({
"requestId": request_id,
"authChallengeResponse": {
"response": "ProvideCredentials",
"username": creds.username,
"password": creds.password
}
})),
None,
).await;
}
}
七、与 Playwright 的对比
| 特性 | agent-browser | Playwright |
|---|---|---|
| 路由语法 | Glob 模式 (**/api/**) |
Regex + Glob |
| 拦截时机 | Fetch.requestPaused |
Fetch.requestPaused |
| 修改响应头 | ✅ | ✅ |
| 修改请求体 | ❌ | ✅ |
| HAR 导出 | ✅ | ✅ |
| 多浏览器 | CDP/WebDriver/Appium | Chromium/Firefox/WebKit |
八、实际应用场景
8.1 前端测试:Mock API
# 启动带路由的浏览器
agent-browser open "https://my-app.com" \
--route "**/api/users" --mock '{"status": 200, "body": "{\"users\":[{\"id\":1,\"name\":\"Test\"}]}"}' \
--route "**/api/orders" --mock '{"status": 500, "body": "{\"error\":\"Server Error\"}"}'
# 测试前端对 500 错误的处理
agent-browser click @submit_order
agent-browser wait-for "Error: Server Error"
8.2 数据采集:绕过反爬
agent-browser open "https://example.com" \
--headers '{"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}' \
--route "**/*analytics*" --abort \
--route "**/*tracking*" --abort
8.3 性能分析:HAR 导出
agent-browser open "https://example.com"
agent-browser har --output performance.har
# 用 har 分析工具查看加载性能
九、总结
agent-browser 的 Network 拦截系统基于 CDP Fetch domain,实现了:
| 功能 | 实现方式 | 价值 |
|---|---|---|
| 请求拦截 | Fetch.enable + Fetch.requestPaused |
精确控制网络流量 |
| Mock 响应 | Fetch.fulfillRequest |
无后端测试 |
| 中断请求 | Fetch.failRequest |
模拟错误场景 |
| 注入 Header | Fetch.continueRequest + headers |
认证、UA 修改 |
| 代理认证 | Fetch.continueWithAuth |
企业代理穿透 |
| HAR 记录 | Network.* 事件追踪 |
性能分析 |
这套系统使得 agent-browser 不仅是浏览器自动化工具,还是一个网络流量操控平台。
系列文章: 1. agent-browser 架构概览:从 CLI 到 CDP 的分层设计 2. CDP WebSocket 客户端实现:命令/响应匹配与事件广播 3. Accessibility Tree 快照原理:如何让 AI 看懂网页 4. Chrome 进程管理与多 Backend 架构 5. Network 拦截与路由:Fetch Domain 实战 ← 本文
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)