十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Puppeteer 请求拦截收尾机制深度解析:HTTPRequest.finalizeInterceptions() 的工作原理与实战

Puppeteer 请求拦截收尾机制深度解析:HTTPRequest.finalizeInterceptions() 的工作原理与实战 Puppeteer 请求拦截收尾机制深度解析HTTPRequest.finalizeInterceptions() 的工作原理与实战【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer请求拦截Request Interception是 Puppeteer 中劫持并改写页面网络请求的核心能力而HTTPRequest.finalizeInterceptions()正是这段拦截流水线的收尾闸门它负责等待所有挂起的拦截处理程序执行完毕再依据拦截协商结果最终决定把请求abort中止、respond伪造响应还是 continue放行。本文以 docs/api/puppeteer.httprequest.finalizeinterceptions.md 为主体结合仓库源码逐行拆解这一方法的执行语义、被谁在何时调用、如何与continue/respond/abort的优先级协商机制协作帮助你彻底理解 Puppeteer以及基于 CDP 与 WebDriver BiDi 的两种实现在网络拦截上的内部闭环进而在编写广告屏蔽、接口 Mock、请求改写等场景时避免踩坑。方法定义一句话看懂的 API按官方 API 文档该方法属于 HTTPRequest 类签名如下class HTTPRequest { finalizeInterceptions(): Promisevoid; }返回类型Promisevoid。其职责在文档中被精确概括为一句Awaits pending interception handlers and then decides how to fulfill the request interception. 等待挂起的拦截处理程序然后决定如何完成该请求的拦截。也就是说finalizeInterceptions并不自己拦截任何东西它做两件事等待—— 把排入队列的、尚未完成的所有拦截处理函数依次执行完并await裁决—— 读取最终协商出的拦截决议intercept resolution state执行对应的底层协议调用放行或干预这个请求。需要指出的是从源码结构与调用链来看这个方法属于 Puppeteer 内部驱动流程的关键一环通常在请求事件分发后由框架自动调用日常业务代码中你很少需要直接调用它——但理解它的机制是掌握page.setRequestInterception(true)之后整条事件链路的前提。源码级解剖它究竟做了什么该方法的真实实现位于 packages/puppeteer-core/src/api/HTTPRequest.ts它是所有平台实现CDP 版CdpHTTPRequest、BiDi 版BidiHTTPRequest共享的抽象基类方法// packages/puppeteer-core/src/api/HTTPRequest.ts async finalizeInterceptions(): Promisevoid { await this.interception.handlers.reduce((promiseChain, interceptAction) { return promiseChain.then(interceptAction); }, Promise.resolve()); this.interception.handlers []; const {action} this.interceptResolutionState(); switch (action) { case abort: return await this._abort(this.interception.abortReason); case respond: if (this.interception.response null) { throw new Error(Response is missing for the interception); } return await this._respond(this.interception.response); case continue: return await this._continue(this.interception.requestOverrides); } }可以把它拆成三个明确的阶段来理解。第一阶段串行排空挂起处理队列每个HTTPRequest实例内部都维护着一个拦截状态对象interception初始化于同一文件 L136-L154其中最关键的是handlers: Array() void | PromiseLikeany队列protected interception: { enabled: boolean; // 是否开启拦截 handled: boolean; // 是否已被实际处理 handlers: Array() void | PromiseLikeany; // 挂起的拦截处理函数 resolutionState: InterceptResolutionState; // 当前协商决议 requestOverrides: ContinueRequestOverrides; // continue 时的改写参数 response: PartialResponseForRequest | null; // respond 时的伪造响应 abortReason: Protocol.Network.ErrorReason | null;// abort 时的失败原因 } { /* ... 初始值 ... */ };finalizeInterceptions的第一步是用reduce把队列里每个 handler 串成一个 Promise 链并整体awaitawait this.interception.handlers.reduce((promiseChain, interceptAction) { return promiseChain.then(interceptAction); }, Promise.resolve());这意味着只有队列中所有挂起的处理函数包括异步函数都执行完毕、resolve 之后最终裁决才会开始。这正是注释里所说的 Deferred handlers ... are guaranteed to resolve before the request interception is finalized延迟处理函数不保证顺序但保证在拦截收尾前全部 resolve。执行完毕后再把队列清空this.interception.handlers []。第二阶段读取协商决议队列清空后代码调用interceptResolutionState()取得当前协商结果const {action} this.interceptResolutionState();该方法同文件 L208-L216会做三种判断若请求未开启拦截!enabled→ 返回{action: InterceptResolutionAction.Disabled}若请求已被某个底层调用实际处理过handled true→ 返回{action: InterceptResolutionAction.AlreadyHandled}否则返回当前协商中的resolutionState初始为None调用带priority的continue/respond/abort后会变为对应动作。完整的决议类型枚举定义在同文件 L587-L594export enum InterceptResolutionAction { Abort abort, Respond respond, Continue continue, Disabled disabled, None none, AlreadyHandled already-handled, }第三阶段按决议执行底层调用拿到action后finalizeInterceptions只对三种值做分发其余值disabled、none、already-handled静默跳过、不做任何处理abort→ 调用内部方法_abort(this.interception.abortReason)中止请求respond→ 先校验this.interception.response非空为空会抛出Response is missing for the interception随后_respond(response)用伪造响应回填continue→ 调用_continue(this.interception.requestOverrides)携带改写参数放行请求。这里的_abort/_respond/_continue均为抽象内部方法由各平台实现提供详见下文两种协议的底层落地。触发时机谁在什么时候调用它finalizeInterceptions并非由用户手动触发而是每次请求被拦截后框架在分发request事件之后立刻调用。从调用点可以清晰还原事件闭环。CDP 实现NetworkManager在 CDP 通道中请求生命周期由 packages/puppeteer-core/src/cdp/NetworkManager.ts 管理。每当浏览器上报新的请求事件NetworkManager 构造CdpHTTPRequest后会先emit(Request)同步通知监听者紧接着在同一 tick 内以 fire-and-forget 方式调用收尾方法// packages/puppeteer-core/src/cdp/NetworkManager.ts const request new CdpHTTPRequest( client, frame, event.requestId, this.#userRequestInterceptionEnabled, event, [], this.#logger, ); this.emit(NetworkManagerEvent.Request, request); void request.finalizeInterceptions();同样的模式还出现在处理重定向后新请求的#onRequest中同文件 L607。BiDi 实现Frame 层在 WebDriver BiDi 通道Firefox 等下请求拦截的装配发生在 packages/puppeteer-core/src/bidi/Frame.ts。浏览上下文收到新请求、封装成BidiHTTPRequest并发出request事件后同样立刻触发收尾// packages/puppeteer-core/src/bidi/Frame.ts request.once(success, () { /* 触发 RequestFinished */ }); request.once(error, () { /* 触发 RequestFailed */ }); void httpRequest.finalizeInterceptions();重定向场景则出现在 packages/puppeteer-core/src/bidi/HTTPRequest.ts 的#initialize()中redirect事件分支L107。可以看到发出 request 事件 → 立刻 finalize是两种协议实现完全一致的骨架这个时序保证了用户回调中做出的拦截决策一定先于最终裁决生效——这正是它能正确工作的前提。与 page.on(request) 的协作enqueueInterceptAction 是关键那么用户通过page.on(request, handler)注册的回调是如何保证在finalizeInterceptions之前执行完成的答案藏在Page的监听器包装逻辑中。在 packages/puppeteer-core/src/api/Page.tsL861 附近的on重载中当监听的事件类型是PageEvent.Request时Puppeteer 会把你的回调包进一个包装函数wrapper (event: HTTPRequest) { event.enqueueInterceptAction(() { return handler(event as ...); // 你的 async/同步回调被推入队列 }); };而enqueueInterceptAction定义在 HTTPRequest.ts L232-L236只是简单地把回调推入interception.handlersenqueueInterceptAction(pendingHandler: () void | PromiseLikeunknown): void { this.interception.handlers.push(pendingHandler); }综合起来一条完整的事件时序是浏览器上报请求NetworkManager/Frame 构造HTTPRequest并emit(request)用户 handler 被包装后同步地入队handler 内部的异步逻辑此时还未执行完emit返回后框架立刻调用void request.finalizeInterceptions()finalizeInterceptions用 Promise 链逐个await队列里的 handler给异步逻辑留出完成时间全部 handler 结束后读取协商决议执行abort/respond/continue中的一种。只要 handler 内部调用了request.continue()/respond()/abort()无论带不带priority或者在handlers中推入了其它任务都能在步骤 4 被完整等待。这就是finalizeInterceptions承担拦截处理程序汇聚点角色的由来——它把一个可能异步、且分散在多个监听器中的决策过程收敛成一次确定的协议层调用。决策细节立即模式与协作优先模式用户在 handler 中做出的决策有两种落地路径理解它们对写出正确的拦截代码至关重要。立即模式不传 priority调用continue(overrides)、respond(response)、abort(errorCode)时若不提供priority参数请求会立刻走底层调用。以continue为例HTTPRequest.ts L426-L460async continue(overrides: ContinueRequestOverrides {}, priority?: number): Promisevoid { this.verifyInterception(); // 未开启拦截或已处理则抛异常 if (!this.canBeIntercepted()) { // data: URL、内存缓存请求不可拦 return; } if (priority undefined) { return await this._continue(overrides); // 立即放行 } // ...priority 协商逻辑 }底层调用成功时会把this.interception.handled true。因此之后finalizeInterceptions读取状态时拿到的是AlreadyHandledswitch 不匹配任何 case直接结束——避免了对同一请求的重复处理。这也是interceptResolutionState()文档注释中already-handled语义的由来见 HTTPRequest.ts L197-L216。协作优先模式传 priority当多个监听器可能同时尝试裁决同一个请求时可给continue/respond/abort传入priority此时方法不立即执行底层操作而是通过比较优先级更新resolutionStateNone初始动作见 L144-L154。默认基准优先级为公开常量export const DEFAULT_INTERCEPT_RESOLUTION_PRIORITY 0;三个方法的比较规则在源码中非常一致归纳如下动作更新逻辑要点源码位置continue(overrides, priority)记录requestOverrides新 priority 更高则决议置为Continue同 priority 时abort/respond优先于continueHTTPRequest.ts L438-L459respond(response, priority)记录伪造response同 priority 时abort优先于respondHTTPRequest.ts L505-L522abort(errorCode, priority)记录abortReason使用同 priority 也能抢占为AbortHTTPRequest.ts L552-L563由于这些调用只是记录状态、不真正触达浏览器最终真正执行底层协议动作的正是finalizeInterceptions第三阶段的 switch 分发。换言之priority 模式下的所有裁决最终都在finalizeInterceptions里统一兑现。如果没有这个收尾方法协作模式记录的状态将永远无法落到真实请求上。两种协议的底层落地finalizeInterceptions只负责分发真正的动手交给抽象内部方法两种平台实现各有一套协议级调用。CDPChrome 的 Fetch 域CdpHTTPRequestpackages/puppeteer-core/src/cdp/HTTPRequest.ts的实现直接对应 Chrome DevTools Protocol 的Fetch域_continueL209-L234携带_interceptionId调用Fetch.continueRequest支持改写url、method、base64 编码的postData与headers_respondL236-L286调用Fetch.fulfillRequest自动根据 body 计算content-length、补齐content-type并将 HTTP 状态码映射为标准状态短语STATUS_TEXTS_abortL288-L304调用Fetch.failRequest将错误码如blockedbyclient、namenotresolved映射为协议层的ErrorReason映射表见 HTTPRequest.ts L716-L731。值得注意的是canBeIntercepted()在 CDP 实现中排除了data:URL 与来自内存缓存的请求L202-L204——这也意味着对这些请求调用裁决方法会被静默跳过。BiDiFirefox 的 Network 域BidiHTTPRequestpackages/puppeteer-core/src/bidi/HTTPRequest.ts则把同样语义落到 WebDriver BiDi 网络协议上_continue调用continueRequest自动把postData转 base64、headers 转 BiDi 结构_respond调用provideResponse_abort调用failRequest。三类实现均在成功后将interception.handled true、失败时回滚为false与 CDP 路径保持一致的已处理标记契约。由此可推断finalizeInterceptions作为一个纯调度层方法天然做到了协议无关——上层 API 文档、协商逻辑只写一份底层的 Chrome/Firefox 差异被完全封装在三个抽象内部方法之后。结合实战理解一段完整的请求拦截示例把上述机制串起来看一段典型的广告/无用资源屏蔽代码源于 Puppeteer 拦截拦截器最基础的用法可作为对照finalizeInterceptions时序的实操模板import puppeteer from puppeteer; const browser await puppeteer.launch(); const page await browser.newPage(); await page.setRequestInterception(true); // 开启拦截见 Page.setRequestInterception page.on(request, request { // 方案 A立即裁决不传 priority立刻执行 if (request.url().endsWith(.png) || request.url().endsWith(.jpg)) { request.abort(); // 立即调用 Fetch.failRequest } else { request.continue(); // 立即调用 Fetch.continueRequest } }); // 方案 B协作优先模式所有 handler 结束后由 finalizeInterceptions 统一裁决 // page.on(request, request { // if (request.isNavigationRequest()) { // request.continue({}, 0); // } else { // request.abort(blockedbyclient, DEFAULT_INTERCEPT_RESOLUTION_PRIORITY); // } // }); await page.goto(https://example.com); await browser.close();无论采用哪种方案底层都会经历同一过程emit(request)→ 你的回调把裁决结果立即模式直接落地协作模式写入resolutionState→finalizeInterceptions()排空 handler 队列 → 读取决议并做最终一次abort/respond/continue。方案 B 中传入的priority正是 DEFAULT_INTERCEPT_RESOLUTION_PRIORITY 或自定义数值多个监听器之间通过它决定谁说了算。相关联的 API 速查围绕finalizeInterceptionsHTTPRequest类还提供了一批语义相关的公开方法与类型便于深入查阅HTTPRequest.enqueueInterceptAction —— 把延迟处理函数推入收尾队列HTTPRequest.interceptResolutionState —— 读取当前协商决议与优先级HTTPRequest.isInterceptResolutionHandled —— 判断请求是否已被实际处理HTTPRequest.continue、HTTPRequest.respond、HTTPRequest.abort —— 三种裁决动作Page.setRequestInterception —— 拦截开关一切的起点。小结finalizeInterceptions()虽只是一句 Awaits pending interception handlers and then decides how to fulfill the request interception 的 API 描述背后却是 Puppeteer 请求拦截架构的枢纽它把异步、分布式的拦截决策汇聚成确定性的单一执行点以reduce串行等待全部挂起 handler再依据interceptResolutionState()的最终结果分发到协议层的_abort/_respond/_continue在 CDPChrome与 BiDiFirefox两条通道上保持完全一致的语义。深入理解这一收尾机制能让你在编写多监听器协作拦截、异步 Mock、请求改写等高阶逻辑时对请求何时被放行、由谁放行、放行成什么做到心中有数。【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表