
深入解析 Puppeteer HTTPRequest 类请求生命周期、拦截终结与重定向链完整指南【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer导读HTTPRequest是 Puppeteer 面向开发者暴露的一次页面网络请求的对象化抽象每当页面发起请求Puppeteer 都会构造一个HTTPRequest实例并派发request相关事件同时通过abort()、continue()、respond()三个拦截终结方法赋予脚本拦截、改写、伪造网络流量的能力。本文将围绕 HTTPRequest 类参考文档 的核心骨架结合 抽象基类源码 与 CDP 实现源码 进行源码级剖析读完你将掌握请求生命周期的判定规则、拦截机制的正确用法、协作式优先级解析原理以及重定向链的取值语义。HTTPRequest一次 HTTP 请求的对象化表示类的定位与类型签名从 API 参考文档可见HTTPRequest在仓库类型系统中被声明为一个抽象类作为统一的门面 API 供上层脚本使用export declare abstract class HTTPRequest在 核心 API 源码 中它是一个带有一组抽象方法定义的抽象基类而从源码结构看它至少有一个具体的子类实现CdpHTTPRequest位于 CDP 层实现extends HTTPRequest。抽象基类把url()、method()、headers()、abort()、continue()、respond()等行为定义为契约把与具体浏览器协议Chrome DevTools Protocol相关的细节隔离在下游实现中这正是 Puppeteer 让同一套 API 同时面向 Chrome 与 Firefox 的架构手段。构造函数的内部性与使用边界文档明确标注该类的构造函数被标记为 internal第三方代码不得直接调用构造函数也不得创建继承HTTPRequest的子类。在源码中可以看到抽象基类的构造函数是空的并被标为internalHTTPRequest.ts#L166/** * internal */ constructor() {}这意味着你的正确用法是在事件回调中接收由 Puppeteer 创建好的实例而不是自行new一个请求对象。请求生命周期request / requestfinished / requestfailed 三事件模型理解HTTPRequest之前先掌握页面请求在 Puppeteer 中的生命周期。文档给出的规则是每当页面发出一次请求例如获取某个网络资源page对象都会发出如下事件request页面发出该请求时触发requestfinished响应体下载完成、请求结束时触发若请求在中途失败则不发requestfinished而是触发requestfailed。上述事件均会携带一个代表该次请求的HTTPRequest实例例如文档中的基本用法page.on(request, request { // request 即 HTTPRequest 实例 });结合 Page 类文档 中相关事件可知完整监听网络过程通常配合response事件使用。关于事件语义有两个容易踩坑但文档明确强调的细节HTTP 错误响应不等于请求失败404、503 这类错误响应从 HTTP 协议角度仍是成功完成的响应因此请求会以requestfinished正常收尾而不会触发requestfailed。判断业务层面的成败应依赖 HTTPResponse 的status()等接口而不是请求事件本身。重定向会正常结束当前请求并新建请求当请求收到重定向响应如 301/302时当前请求以requestfinished成功结束随后 Puppeteer 会对重定向后的新 URL 发出一个新请求新请求实例随之产生。这一模型与下文redirectChain()的语义直接对应。HTTPRequest 的属性与全部方法速览文档为HTTPRequest定义了一个只读属性和一组方法先给出一张速览总表后续各节再逐类深入。成员类型作用一句话描述clientreadonlyExperimentalCDPSession直接访问底层 CDP 会话文档警告使用它可能破坏 Puppeteer请谨慎url()string请求的 URLmethod()string请求方法GET、POST等headers()Recordstring, string请求头对象所有头名均为小写resourceType()ResourceType渲染引擎感知到的资源类型如document、imageinitiator()Protocol.Network.Initiator \| undefined请求的发起者信息frame()Frame \| null发起请求的 frame导航到错误页时为nullisNavigationRequest()boolean该请求是否为当前 frame 导航的驱动请求response()HTTPResponse \| null匹配的响应对象尚未收到响应时为nullpostData()deprecatedstring \| undefined已废弃改用fetchPostData()hasPostData()boolean请求是否带有 POST 数据fetchPostData()Promisestring \| undefined从浏览器端主动拉取 POST 数据redirectChain()HTTPRequest[]获取资源所经历的重定向请求链failure(){errorText: string} \| null请求失败信息未失败则为nullabort()Promisevoid中止请求需先开启拦截continue()Promisevoid以可选覆盖项放行请求需先开启拦截respond()Promisevoid以伪造的响应满足请求需先开启拦截abortErrorReason()内部状态查询最近一次中止该请求的错误原因continueRequestOverrides()内部状态查询将用于放行请求的ContinueRequestOverridesresponseForRequest()内部状态查询将用于响应该请求的ResponseForRequestinterceptResolutionState()内部状态查询当前拦截解析动作与优先级isInterceptResolutionHandled()内部状态查询拦截解析是否已被处理enqueueInterceptAction()内部机制向处理队列追加异步拦截处理器finalizeInterceptions()内部机制等待队列后按最终状态终结拦截属性方面唯一暴露的是实验性的clientgetter 声明见 HTTPRequest.ts#L161在CdpHTTPRequest中它返回底层 CDP 会话cdp/HTTPRequest.ts#L50-L52。文档在参考页和类注释中反复提示Experimental / Warning直接操作该 client 可能绕过 Puppeteer 的状态管理而破坏其内部一致性日常开发不建议依赖。请求信息读取类方法详解url() / method() / headers() / resourceType()这四个方法描述一次请求的基本事实url()请求的完整 URL 字符串method()请求方法如GET、POSTheaders()返回键值对象所有 header 名称一律转为小写源码与文档均如此声明例如可通过request.headers()[content-type]读取而大小写混写会取不到值resourceType()返回渲染引擎对资源类型的感知结果是字符串字面量联合类型。可用的ResourceType在 HTTPRequest.ts#L65 定义为LowercaseProtocol.Network.ResourceType即取 CDP 中Network.ResourceType全小写形式实际取值包含document、stylesheet、image、media、font、script、texttrack、xhr、fetch、prefetch、eventsource、websocket、manifest、signedexchange、ping、cspviolationreport、preflight、other等。这是实现按类型拦截如屏蔽所有图片的常用判据仓库中的 block-images.js 示例 即按此模式编写page.on(request, request { if (request.resourceType() image) { request.abort(); } else { request.continue(); } });initiator() / frame() / isNavigationRequest()initiator()返回请求的发起者描述直接映射 CDP 的Network.Initiator包含type如parser、script、other、发起 script 的 URL、行号列号以及可选的url与lineNumber等字段可用于诊断这个请求是谁发起的frame()返回发起请求的 frame若请求发生在错误页导航场景返回nullisNavigationRequest()true表示该请求驱动了当前 frame 的导航典型如主文档请求可用于区分导航请求与页面内部的资源子请求。response()在requestfinished前请求对象尚未关联到响应。response()返回与该请求匹配的 HTTPResponse 对象若响应尚未收到则返回null。典型的配合用法是在requestfinished或response事件中通过request.response()拿状态码。POST 数据postData() 的废弃与 fetchPostData() / hasPostData()文档为postData()明确标注了deprecated指引开发者改用fetchPostData()因为新方法会主动向浏览器发起一次协议调用去抓取数据而非依赖事件载荷中的缓存快照。这一点由 HTTPRequest.ts#L290-L305 中两个抽象方法签名相互印证。尤其需要注意的是hasPostData()的语义陷阱文档强调当该标志为true时postData()仍可能返回undefined——如果数据过长、或当前不易以解码形态获取此时应使用fetchPostData()。也就是说先用hasPostData()快速判断请求是否携带 body要拿到实际内容时优先走fetchPostData()不要在hasPostData() true时断言postData()一定有值。请求拦截机制abort / continue / respond 三件套前置条件必须先开启请求拦截abort()、continue()、respond()三个终结方法都要求先通过page.setRequestInterception(true)开启拦截否则方法会立即抛异常。开启拦截的能力与语义参见 Page.setRequestInterception 文档一旦开启页面上的每个请求都会停滞直到你调用三者之一对其放行/终结或请求改由浏览器缓存完成。底层校验逻辑集中在抽象基类的verifyInterception()HTTPRequest.ts#L389-L392protected verifyInterception(): void { assert(this.interception.enabled, Request Interception is not enabled!); assert(!this.interception.handled, Request is already handled!); }它同时做了两道检查其一拦截未开启会抛出Request Interception is not enabled!其二同一请求已被处理过会抛出Request is already handled!——即一个请求只能被终结一次不能在abort()之后又去continue()。continue()放行并可选改写请求方法签名见 continue 子文档class HTTPRequest { continue( overrides?: ContinueRequestOverrides, priority?: number, ): Promisevoid; }ContinueRequestOverrides可选覆盖项定义见 HTTPRequest.ts#L22-L30字段表见 ContinueRequestOverrides 文档字段类型说明urlstring若设置请求 URL 会改变。注意这不是重定向只是改写目标地址methodstring改写请求方法postDatastring改写 POST bodyheadersRecordstring, string改写请求头文档给出的典型场景是在请求发出前覆盖或移除 header。实现要点是先用Object.assign基于request.headers()拷贝一份小写头对象再覆写目标字段把要删除的头置为undefined最后整体传入await page.setRequestInterception(true); page.on(request, request { // Override headers const headers Object.assign({}, request.headers(), { foo: bar, // set foo header origin: undefined, // remove origin header }); request.continue({headers}); });如果没有任何覆盖需求直接request.continue()即可放行。respond()以伪造响应满足请求方法签名见 respond 子文档class HTTPRequest { respond( response: PartialResponseForRequest, priority?: number, ): Promisevoid; }ResponseForRequest的必填语义由 HTTPRequest.ts#L45-L58 给出接口类型上status、headers、contentType、body均为必填但由于方法入参是PartialResponseForRequest实际常传入省略默认值的子集。body支持string或Uint8Arrayheaders的取值会被统一转成字符串其中数组值会被逐个映射同一名字需要多个值时使用数组。因为参数允许Partial所以至少要给出一个能构造响应的最小集。文档中的用 404 满足所有请求示例await page.setRequestInterception(true); page.on(request, request { request.respond({ status: 404, contentType: text/plain, body: Not Found!, }); });这里未显式传headers说明底层允许缺省、由实现补齐。一个必须记住的边界条件文档明确强调对data:URL 请求的 mock 响应不被支持对这类请求调用respond()是 noop空操作不会生效也不报错。响应体的编码细节见抽象类中的静态工具HTTPRequest.getResponse()HTTPRequest.ts#L568-L581它对字符串用TextEncoder编码以计算正确的contentLength字节数而非字符数再以 base64 形式交给协议层下发。abort()中止请求方法签名见 abort 子文档class HTTPRequest { abort(errorCode?: ErrorCode, priority?: number): Promisevoid; }errorCode缺省值为failedHTTPRequest.ts#L540。可用取值即 ErrorCode 文档 列出的字符串字面量联合定义见 HTTPRequest.ts#L599-L613aborted、accessdenied、addressunreachable、blockedbyclient、blockedbyresponse、connectionaborted、connectionclosed、connectionfailed、connectionrefused、connectionreset、internetdisconnected、namenotresolved、timedout、failed。这些 Puppeteer 层面的代码会在 errorReasons 映射表 中被翻译成 CDP 的Network.ErrorReason如namenotresolved→NameNotResolved非法代码会通过assert(errorReason, Unknown error code: ...)抛错拒绝。最常见的用法是屏蔽某种资源例如request.abort()默认即等价于以 failed 中止也可按需指定更精确的原因如拦截跨域外的第三方请求时用request.abort(blockedbyclient)模拟客户端侧屏蔽。完整的仓库运行示例见 block-images.jsimport puppeteer from puppeteer; const browser await puppeteer.launch(); const page await browser.newPage(); await page.setRequestInterception(true); page.on(request, request { if (request.resourceType() image) { request.abort(); } else { request.continue(); } }); await page.goto(https://news.google.com/news/); await page.screenshot({path: news.png, fullPage: true}); await browser.close();协作式优先级拦截priority 参数与解析规则细心的读者会注意到abort/continue/respond都有第二个可选参数priority。这正是 Puppeteer 支持的协作式拦截cooperative interception多个脚本/过滤器可同时注册对同一请求的处理意图最终由优先级仲裁谁说了算。优先级仲裁的核心状态在 HTTPRequest.ts#L136-L154 的interception内部对象中维护包含启用标志、已处理标志、处理器队列、解析状态action priority、请求覆盖项、伪造响应与中止原因。公共的默认优先级常量DEFAULT_INTERCEPT_RESOLUTION_PRIORITY 0定义于 HTTPRequest.ts#L72。三者的协作规则以continue()为例见 HTTPRequest.ts#L426-L460respond()与abort()逻辑同构未提供priority立即解析立即执行对应终结动作不进入协作仲裁。这是最常见的用法。提供了priority进入协作模式——方法只登记意图覆盖项/响应/中止原因与优先级由更高的仲裁层在适当时机统一收口只有当新意图的优先级高于当前记录优先级时才覆盖解析动作同优先级下abort与respond优先于continue中止又优先于 respond同优先级下已存在abort则respond让位见 HTTPRequest.ts#L516-L518abort()在同优先级下用判定HTTPRequest.ts#L555使后登记的同级中止仍可生效。拦截状态的查询四件套配合协作式解析抽象基类提供了四个只读查询方法对应文档中的方法条目interceptResolutionState()返回InterceptResolutionState含action与可选priority见 InterceptResolutionState 文档。action的取值是 InterceptResolutionAction 枚举HTTPRequest.ts#L587-L594abort、respond、continue、disabled、none、already-handled。实现的细节在 HTTPRequest.ts#L208-L216拦截未启用时返回{action: disabled}已被处理时返回{action: already-handled}否则返回登记的解析状态。isInterceptResolutionHandled()返回true表示拦截解析已被处理即已被终结过一次反之false。abortErrorReason()返回最近一次调abort()时登记的中止原因CDPNetwork.ErrorReason未登记则为null。continueRequestOverrides()与responseForRequest()分别返回若被放行将使用的覆盖项与若被响应将使用的伪造响应见 responseforrequest 相关实现。这些方法在文档中对应abortErrorReason()、continueRequestOverrides()、responseForRequest()、interceptResolutionState()、isInterceptResolutionHandled()五个条目它们主要用于诊断当前这个被拦截的请求将走向哪个结局是排查复杂拦截器组合问题的利器。收口机制enqueueInterceptAction() 与 finalizeInterceptions()在协作式场景下终结动作不是立即执行的而是通过内部队列统一收口这正是文档中两个方法条目的职责enqueueInterceptAction(pendingHandler)向处理队列追加一个异步处理器见 HTTPRequest.ts#L232-L236。文档强调追加的处理器不保证按特定顺序执行但保证在拦截被终结之前全部 resolve。finalizeInterceptions()先以reduce将队列中的处理器串成 promise 链依次 awaitHTTPRequest.ts#L259-L276清空队列然后按最终interceptResolutionState()的动作分派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: // response 为 null 时抛出 Response is missing for the interception return await this._respond(this.interception.response); case continue: return await this._continue(this.interception.requestOverrides); } }从这里可以看到抽象基类预留的三个internal抽象方法_abort/_respond/_continueHTTPRequest.ts#L241-L253它们由下游如CdpHTTPRequest翻译成具体的协议调用公共层的abort/respond/continue方法在此之上完成校验、协作仲裁与参数登记。另外真实协议调用过程中会遇到部分可容忍的错误如请求已被取消或页面已关闭基类为此准备了错误处理器handleErrorHTTPRequest.ts#L736-L752它会放行非法 header / 不安全 header / 非法参数类错误其余则仅记录日志而不抛出——这也是拦截器代码中常见偶发静默失败的底层来源之一。failure() 与重定向链 redirectChain()请求失败信息failure()用于访问请求的失败信息签名与语义见 failure 子文档未失败时返回null失败时返回{errorText: string}其中errorText是如net::ERR_FAILED的人类可读错误信息。同时文档提醒请求失败时并不保证一定有失败文本因此代码上需要对可能为null/缺失的情况保持健壮。官方示例——记录所有失败请求page.on(requestfailed, request { console.log(request.url() request.failure().errorText); });注意这里配合的是requestfailed事件与第一节失败才触发requestfailed、HTTP 错误码不触发的模型闭环对应。redirectChain()一次资源获取背后的请求链redirectChain()返回为获取某一资源而发起的一串请求。文档给出的两个示例非常直观存在单次重定向http://example.com重定向到https://example.com时链中包含一个请求即最初的http://example.com请求const response await page.goto(http://example.com); const chain response.request().redirectChain(); console.log(chain.length); // 1 console.log(chain[0].url()); // http://example.com没有重定向时链为空const response await page.goto(https://google.com); const chain response.request().redirectChain(); console.log(chain.length); // 0两个关键语义源码与文档一致链中存放的是被重定向的旧请求链长度等于发生了多少次跳转重定向到https的落地请求本身不在链内它才是response.request()redirectChain在同一条链的所有请求间共享见 HTTPRequest.ts#L336-L362即链上任何一个请求拿到的redirectChain()都是同一个数组引用从源码结构看这一共享由基类的_redirectChain: HTTPRequest[]字段HTTPRequest.ts#L131在创建新请求时透传实现。底层映射CdpHTTPRequest 的字段承载在 ChromeCDP路径下HTTPRequest的每一个抽象方法都由 CdpHTTPRequest 落地。其字段实现cdp/HTTPRequest.ts#L32-L52揭示了抽象 API 与 CDP 事件载荷的对应关系#url、#method、#headers、#resourceType、#frame、#initiator、#hasPostData、#postData由构造时传入的 CDP 事件参数快照而来分别支撑url()、method()、headers()、resourceType()、frame()、initiator()、hasPostData()/postData()等读取方法#client支撑实验性的clientgetter含一个配套 setter供会话迁移场景使用见 cdp/HTTPRequest.ts#L50-L56#isNavigationRequest支撑isNavigationRequest()来自基类继承的_interceptionId、_failureText、_response、_fromMemoryCache、_redirectChain等内部字段HTTPRequest.ts#L115-L131则记录了拦截 ID、失败文本、关联响应、是否命中内存缓存与共享重定向链。此外公共层导出的headersArray()HTTPRequest.ts#L623-L641负责把Recordstring, string | string[]拍平成{name, value}数组数组值逐项展开供下游把 JS 对象转成协议要求的头列表STATUS_TEXTS常量表HTTPRequest.ts#L650-L714则维护了标准状态码到状态短语的映射基于 IANA 注册表并额外收录 306 与 418用于伪造响应时补全状态文本。这些细节说明调用一次request.respond({status: 404, contentType: text/plain, body: Not Found!})背后要经历参数登记 → 协作仲裁 → CDP 协议下发 → base64 编码与状态短语补齐一整条链路。实践要点小结围绕HTTPRequest本文覆盖的内容可以收敛为以下可直接落地的经验事件选型监听request做拦截与改写用requestfinished/requestfailed判断传输是否成功不要用 4xx/5xx 状态码判定请求失败。拦截前置任何abort()/continue()/respond()调用前都必须page.setRequestInterception(true)否则立即抛异常且同一请求只能终结一次第二次调用会触发Request is already handled!。三种结局放行改写用continue()伪造响应用respond()data:URL 无效彻底阻断用abort()。多拦截器共存同一请求可能被多个逻辑处理时给各自调用传入priority走协作式仲裁数值大者胜出、同级abortrespondcontinue需要确认最终走向时查询interceptResolutionState()/isInterceptResolutionHandled()。POST 数据判断有无用hasPostData()读取内容用fetchPostData()避免使用已废弃且可能取不到值的postData()。重定向语义redirectChain()返回被跳过的旧请求序列且各请求共享同一数组链为空表示没有发生重定向。想进一步验证以上行为的读者可继续阅读仓库中的 HTTPRequest 类参考、Page.setRequestInterception 参考 与 抽象基类源码并参考 block-images.js 运行一个完整的拦截-终结最小示例。【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考