Skip to main content
Version: 23.8.0

请求拦截

¥Request Interception

一旦启用请求拦截,每个请求都将停止,除非它继续、响应或中止。

¥Once request interception is enabled, every request will stall unless it's continued, responded or aborted.

中止所有图片请求的简单请求拦截器的示例:

¥An example of a naïve request interceptor that aborts all image requests:

import puppeteer from 'puppeteer';

(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', interceptedRequest => {
if (interceptedRequest.isInterceptResolutionHandled()) return;
if (
interceptedRequest.url().endsWith('.png') ||
interceptedRequest.url().endsWith('.jpg')
)
interceptedRequest.abort();
else interceptedRequest.continue();
});
await page.goto('https://example.com');
await browser.close();
})();

多个拦截处理程序和异步解析

¥Multiple Intercept Handlers and Asynchronous Resolutions

默认情况下,如果在调用其中任何一个之后调用 request.abortrequest.continuerequest.respond,Puppeteer 将引发 Request is already handled! 异常。

¥By default Puppeteer will raise a Request is already handled! exception if request.abort, request.continue, or request.respond are called after any of them have already been called.

始终假设未知处理程序可能已经调用了 abort/continue/respond。即使你的处理程序是你注册的唯一处理程序,第 3 方包也可能会注册自己的处理程序。因此,在调用 abort/continue/respond 之前始终使用 request.isInterceptResolutionHandled 检查解析状态非常重要。

¥Always assume that an unknown handler may have already called abort/continue/respond. Even if your handler is the only one you registered, 3rd party packages may register their own handlers. It is therefore important to always check the resolution status using request.isInterceptResolutionHandled before calling abort/continue/respond.

重要的是,当你的处理程序正在等待异步操作时,拦截解析可能会由另一个监听器处理。因此,request.isInterceptResolutionHandled 的返回值仅在同步代码块中是安全的。始终同时执行 request.isInterceptResolutionHandledabort/continue/respond

¥Importantly, the intercept resolution may get handled by another listener while your handler is awaiting an asynchronous operation. Therefore, the return value of request.isInterceptResolutionHandled is only safe in a synchronous code block. Always execute request.isInterceptResolutionHandled and abort/continue/respond synchronously together.

此示例演示了两个同步处理程序一起工作:

¥This example demonstrates two synchronous handlers working together:

/*
This first handler will succeed in calling request.continue because the request interception has never been resolved.
*/
page.on('request', interceptedRequest => {
if (interceptedRequest.isInterceptResolutionHandled()) return;
interceptedRequest.continue();
});

/*
This second handler will return before calling request.abort because request.continue was already
called by the first handler.
*/
page.on('request', interceptedRequest => {
if (interceptedRequest.isInterceptResolutionHandled()) return;
interceptedRequest.abort();
});

此示例演示了异步处理程序的协同工作:

¥This example demonstrates asynchronous handlers working together:

/*
This first handler will succeed in calling request.continue because the request interception has never been resolved.
*/
page.on('request', interceptedRequest => {
// The interception has not been handled yet. Control will pass through this guard.
if (interceptedRequest.isInterceptResolutionHandled()) return;

// It is not strictly necessary to return a promise, but doing so will allow Puppeteer to await this handler.
return new Promise(resolve => {
// Continue after 500ms
setTimeout(() => {
// Inside, check synchronously to verify that the intercept wasn't handled already.
// It might have been handled during the 500ms while the other handler awaited an async op of its own.
if (interceptedRequest.isInterceptResolutionHandled()) {
resolve();
return;
}
interceptedRequest.continue();
resolve();
}, 500);
});
});
page.on('request', async interceptedRequest => {
// The interception has not been handled yet. Control will pass through this guard.
if (interceptedRequest.isInterceptResolutionHandled()) return;

await someLongAsyncOperation();
// The interception *MIGHT* have been handled by the first handler, we can't be sure.
// Therefore, we must check again before calling continue() or we risk Puppeteer raising an exception.
if (interceptedRequest.isInterceptResolutionHandled()) return;
interceptedRequest.continue();
});

为了进行更细粒度的自省(参见下面的协作拦截模式),你也可以在使用 abort/continue/respond 之前同步调用 request.interceptResolutionState

¥For finer-grained introspection (see Cooperative Intercept Mode below), you may also call request.interceptResolutionState synchronously before using abort/continue/respond.

这是上面使用 request.interceptResolutionState 重写的示例

¥Here is the example above rewritten using request.interceptResolutionState

/*
This first handler will succeed in calling request.continue because the request interception has never been resolved.
*/
page.on('request', interceptedRequest => {
// The interception has not been handled yet. Control will pass through this guard.
const {action} = interceptedRequest.interceptResolutionState();
if (action === InterceptResolutionAction.AlreadyHandled) return;

// It is not strictly necessary to return a promise, but doing so will allow Puppeteer to await this handler.
return new Promise(resolve => {
// Continue after 500ms
setTimeout(() => {
// Inside, check synchronously to verify that the intercept wasn't handled already.
// It might have been handled during the 500ms while the other handler awaited an async op of its own.
const {action} = interceptedRequest.interceptResolutionState();
if (action === InterceptResolutionAction.AlreadyHandled) {
resolve();
return;
}
interceptedRequest.continue();
resolve();
}, 500);
});
});
page.on('request', async interceptedRequest => {
// The interception has not been handled yet. Control will pass through this guard.
if (
interceptedRequest.interceptResolutionState().action ===
InterceptResolutionAction.AlreadyHandled
)
return;

await someLongAsyncOperation();
// The interception *MIGHT* have been handled by the first handler, we can't be sure.
// Therefore, we must check again before calling continue() or we risk Puppeteer raising an exception.
if (
interceptedRequest.interceptResolutionState().action ===
InterceptResolutionAction.AlreadyHandled
)
return;
interceptedRequest.continue();
});

合作拦截模式

¥Cooperative Intercept Mode

request.abortrequest.continuerequest.respond 可以接受可选的 priority 以在协作拦截模式下工作。当所有处理程序都使用协作拦截模式时,Puppeteer 保证所有拦截处理程序都将运行并按注册顺序等待。拦截将被解析为最高优先级的解析。合作拦截模式的规则如下:

¥request.abort, request.continue, and request.respond can accept an optional priority to work in Cooperative Intercept Mode. When all handlers are using Cooperative Intercept Mode, Puppeteer guarantees that all intercept handlers will run and be awaited in order of registration. The interception is resolved to the highest-priority resolution. Here are the rules of Cooperative Intercept Mode:

  • 所有解析都必须向 abort/continue/respond 提供数字 priority 参数。

    ¥All resolutions must supply a numeric priority argument to abort/continue/respond.

  • 如果任何解析不提供数字 priority,则传统模式处于活动状态,而协作拦截模式处于非活动状态。

    ¥If any resolution does not supply a numeric priority, Legacy Mode is active and Cooperative Intercept Mode is inactive.

  • 异步处理程序在拦截解析完成之前完成。

    ¥Async handlers finish before intercept resolution is finalized.

  • 最高优先级拦截决议 "wins",即根据最高优先级的决议最终中止/响应/继续拦截。

    ¥The highest priority interception resolution "wins", i.e. the interception is ultimately aborted/responded/continued according to which resolution was given the highest priority.

  • 如果平局,abort > respond > continue

    ¥In the event of a tie, abort > respond > continue.

为了标准化,指定协作拦截模式优先级时使用 0DEFAULT_INTERCEPT_RESOLUTION_PRIORITY(从 HTTPRequest 导出),除非你有明确的理由使用更高的优先级。这会优雅地优先选择 respond 而不是 continue,优先选择 abort 而不是 respond,并允许其他处理程序协作工作。如果你确实想要使用不同的优先级,则较高的优先级会胜过较低的优先级。允许负优先级。例如,continue({}, 4) 将战胜 continue({}, -2)

¥For standardization, when specifying a Cooperative Intercept Mode priority use 0 or DEFAULT_INTERCEPT_RESOLUTION_PRIORITY (exported from HTTPRequest) unless you have a clear reason to use a higher priority. This gracefully prefers respond over continue and abort over respond and allows other handlers to work cooperatively. If you do intentionally want to use a different priority, higher priorities win over lower priorities. Negative priorities are allowed. For example, continue({}, 4) would win over continue({}, -2).

为了保持向后兼容性,任何在不指定 priority(传统模式)的情况下解析拦截的处理程序都会导致立即解析。为了使合作拦截模式发挥作用,所有解析都必须使用 priority。实际上,这意味着你仍然必须测试 request.isInterceptResolutionHandled,因为你无法控制的处理程序可能在没有优先级的情况下调用了 abort/continue/respond(传统模式)。

¥To preserve backward compatibility, any handler resolving the intercept without specifying priority (Legacy Mode) causes immediate resolution. For Cooperative Intercept Mode to work, all resolutions must use a priority. In practice, this means you must still test for request.isInterceptResolutionHandled because a handler beyond your control may have called abort/continue/respond without a priority (Legacy Mode).

在此示例中,传统模式占主导地位,并且请求立即中止,因为至少有一个处理程序在解析拦截时省略了 priority

¥In this example, Legacy Mode prevails and the request is aborted immediately because at least one handler omits priority when resolving the intercept:

// Final outcome: immediate abort()
page.setRequestInterception(true);
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;

// Legacy Mode: interception is aborted immediately.
request.abort('failed');
});
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;
// Control will never reach this point because the request was already aborted in Legacy Mode

// Cooperative Intercept Mode: votes for continue at priority 0.
request.continue({}, 0);
});

在此示例中,传统模式优先并且请求继续,因为至少一个处理程序未指定 priority

¥In this example, Legacy Mode prevails and the request is continued because at least one handler does not specify a priority:

// Final outcome: immediate continue()
page.setRequestInterception(true);
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;

// Cooperative Intercept Mode: votes to abort at priority 0.
request.abort('failed', 0);
});
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;

// Control reaches this point because the request was cooperatively aborted which postpones resolution.

// { action: InterceptResolutionAction.Abort, priority: 0 }, because abort @ 0 is the current winning resolution
console.log(request.interceptResolutionState());

// Legacy Mode: intercept continues immediately.
request.continue({});
});
page.on('request', request => {
// { action: InterceptResolutionAction.AlreadyHandled }, because continue in Legacy Mode was called
console.log(request.interceptResolutionState());
});

在此示例中,协作拦截模式处于活动状态,因为所有处理程序都指定了 prioritycontinue() 获胜,因为它的优先级高于 abort()

¥In this example, Cooperative Intercept Mode is active because all handlers specify a priority. continue() wins because it has a higher priority than abort().

// Final outcome: cooperative continue() @ 5
page.setRequestInterception(true);
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;

// Cooperative Intercept Mode: votes to abort at priority 10
request.abort('failed', 0);
});
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;

// Cooperative Intercept Mode: votes to continue at priority 5
request.continue(request.continueRequestOverrides(), 5);
});
page.on('request', request => {
// { action: InterceptResolutionAction.Continue, priority: 5 }, because continue @ 5 > abort @ 0
console.log(request.interceptResolutionState());
});

在此示例中,协作拦截模式处于活动状态,因为所有处理程序都指定 priorityrespond() 获胜,因为它的优先级与 continue() 相关,但 respond() 击败 continue()

¥In this example, Cooperative Intercept Mode is active because all handlers specify priority. respond() wins because its priority ties with continue(), but respond() beats continue().

// Final outcome: cooperative respond() @ 15
page.setRequestInterception(true);
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;

// Cooperative Intercept Mode: votes to abort at priority 10
request.abort('failed', 10);
});
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;

// Cooperative Intercept Mode: votes to continue at priority 15
request.continue(request.continueRequestOverrides(), 15);
});
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;

// Cooperative Intercept Mode: votes to respond at priority 15
request.respond(request.responseForRequest(), 15);
});
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;

// Cooperative Intercept Mode: votes to respond at priority 12
request.respond(request.responseForRequest(), 12);
});
page.on('request', request => {
// { action: InterceptResolutionAction.Respond, priority: 15 }, because respond @ 15 > continue @ 15 > respond @ 12 > abort @ 10
console.log(request.interceptResolutionState());
});

合作请求继续

¥Cooperative Request Continuation

Puppeteer 要求显式调用 request.continue(),否则请求将挂起。即使你的处理程序不打算采取任何特殊操作,或者仍必须调用 '选择退出'、request.continue()

¥Puppeteer requires request.continue() to be called explicitly or the request will hang. Even if your handler means to take no special action, or 'opt out', request.continue() must still be called.

随着协作拦截模式的引入,协作请求延续出现了两个用例:无主见和有主见。

¥With the introduction of Cooperative Intercept Mode, two use cases arise for cooperative request continuations: Unopinionated and Opinionated.

第一种情况(常见)是你的处理程序意味着选择不执行任何特殊请求。它对进一步的行动没有意见,只是打算默认继续和/或遵循可能有意见的其他处理程序。但如果没有其他处理程序,我们必须调用 request.continue() 以确保请求不会挂起。

¥The first case (common) is that your handler means to opt out of doing anything special the request. It has no opinion on further action and simply intends to continue by default and/or defer to other handlers that might have an opinion. But in case there are no other handlers, we must call request.continue() to ensure that the request doesn't hang.

我们将其称为“Unopinionated 延续”,因为其目的是在没有其他人有更好想法的情况下继续请求。对于此类延续,请使用 request.continue({...}, DEFAULT_INTERCEPT_RESOLUTION_PRIORITY)(或 0)。

¥We call this an Unopinionated continuation because the intent is to continue the request if nobody else has a better idea. Use request.continue({...}, DEFAULT_INTERCEPT_RESOLUTION_PRIORITY) (or 0) for this type of continuation.

第二种情况(不常见)是,你的处理程序实际上确实有意见和手段,可以通过覆盖其他地方发布的较低优先级 abort()respond() 来强制继续。我们称之为“有态度的延续”。在极少数情况下,如果你打算指定压倒一切的延续优先级,请使用自定义优先级。

¥The second case (uncommon) is that your handler actually does have an opinion and means to force continuation by overriding a lower-priority abort() or respond() issued elsewhere. We call this an Opinionated continuation. In these rare cases where you mean to specify an overriding continuation priority, use a custom priority.

总而言之,请推断你对 request.continue 的使用是否只是默认/绕过行为,还是属于处理程序的预期用例。考虑对范围内的用例使用自定义优先级,否则使用默认优先级。请注意,你的处理者可能同时存在有态度和不有态度的情况。

¥To summarize, reason through whether your use of request.continue is just meant to be default/bypass behavior vs falling within the intended use case of your handler. Consider using a custom priority for in-scope use cases, and a default priority otherwise. Be aware that your handler may have both Opinionated and Unopinionated cases.

升级到包维护者的合作拦截模式

¥Upgrading to Cooperative Intercept Mode for package maintainers

如果你是包维护者并且你的包使用拦截处理程序,则可以更新拦截处理程序以使用协作拦截模式。假设你有以下现有处理程序:

¥If you are package maintainer and your package uses intercept handlers, you can update your intercept handlers to use Cooperative Intercept Mode. Suppose you have the following existing handler:

page.on('request', interceptedRequest => {
if (request.isInterceptResolutionHandled()) return;
if (
interceptedRequest.url().endsWith('.png') ||
interceptedRequest.url().endsWith('.jpg')
)
interceptedRequest.abort();
else interceptedRequest.continue();
});

要使用协作拦截模式,请升级 continue()abort()

¥To use Cooperative Intercept Mode, upgrade continue() and abort():

page.on('request', interceptedRequest => {
if (request.isInterceptResolutionHandled()) return;
if (
interceptedRequest.url().endsWith('.png') ||
interceptedRequest.url().endsWith('.jpg')
)
interceptedRequest.abort('failed', 0);
else
interceptedRequest.continue(
interceptedRequest.continueRequestOverrides(),
0,
);
});

通过这些简单的升级,你的处理程序现在改用协作拦截模式。

¥With those simple upgrades, your handler now uses Cooperative Intercept Mode instead.

但是,我们建议使用稍微更强大的解决方案,因为上面引入了几个微妙的问题:

¥However, we recommend a slightly more robust solution because the above introduces several subtle issues:

  1. 向后兼容性。如果任何处理程序仍然使用传统模式解析(即,未指定优先级),则即使你的处理程序首先运行,该处理程序也会立即解析拦截。这可能会给你的用户带来令人不安的行为,因为你的处理程序突然无法解析拦截,并且当用户所做的只是升级你的软件包时,不同的处理程序会优先处理。

    ¥Backward compatibility. If any handler still uses a Legacy Mode resolution (ie, does not specify a priority), that handler will resolve the interception immediately even if your handler runs first. This could cause disconcerting behavior for your users because suddenly your handler is not resolving the interception and a different handler is taking priority when all the user did was upgrade your package.

  2. 硬编码优先级。你的包用户无法为你的处理程序指定默认解析优先级。当用户希望根据用例操纵优先级时,这可能变得很重要。例如,一个用户可能希望你的包具有高优先级,而另一用户可能希望它具有低优先级。

    ¥Hard-coded priority. Your package user has no ability to specify the default resolution priority for your handlers. This can become important when the user wishes to manipulate the priorities based on use case. For example, one user might want your package to take a high priority while another user might want it to take a low priority.

要解决这两个问题,我们建议的方法是从包中导出 setInterceptResolutionConfig()。然后,用户可以调用 setInterceptResolutionConfig() 来显式激活程序包中的协作拦截模式,这样他们就不会因拦截解决方式的变化而感到惊讶。他们还可以选择使用适合其用例的 setInterceptResolutionConfig(priority) 指定自定义优先级:

¥To resolve both of these issues, our recommended approach is to export a setInterceptResolutionConfig() from your package. The user can then call setInterceptResolutionConfig() to explicitly activate Cooperative Intercept Mode in your package so they aren't surprised by changes in how the interception is resolved. They can also optionally specify a custom priority using setInterceptResolutionConfig(priority) that works for their use case:

// Defaults to undefined which preserves Legacy Mode behavior
let _priority = undefined;

// Export a module configuration function
export const setInterceptResolutionConfig = (priority = 0) =>
(_priority = priority);

/**

* Note that this handler uses `DEFAULT_INTERCEPT_RESOLUTION_PRIORITY` to "pass" on this request. It is important to use

* the default priority when your handler has no opinion on the request and the intent is to continue() by default.
*/
page.on('request', interceptedRequest => {
if (request.isInterceptResolutionHandled()) return;
if (
interceptedRequest.url().endsWith('.png') ||
interceptedRequest.url().endsWith('.jpg')
)
interceptedRequest.abort('failed', _priority);
else
interceptedRequest.continue(
interceptedRequest.continueRequestOverrides(),
DEFAULT_INTERCEPT_RESOLUTION_PRIORITY, // Unopinionated continuation
);
});

如果你的包需要对解析优先级进行更细粒度的控制,请使用如下配置模式:

¥If your package calls for more fine-grained control over resolution priorities, use a config pattern like this:

interface InterceptResolutionConfig {
abortPriority?: number;
continuePriority?: number;
}

// This approach supports multiple priorities based on situational
// differences. You could, for example, create a config that
// allowed separate priorities for PNG vs JPG.
const DEFAULT_CONFIG: InterceptResolutionConfig = {
abortPriority: undefined, // Default to Legacy Mode
continuePriority: undefined, // Default to Legacy Mode
};

// Defaults to undefined which preserves Legacy Mode behavior
let _config: Partial<InterceptResolutionConfig> = {};

export const setInterceptResolutionConfig = (
config: InterceptResolutionConfig,
) => (_config = {...DEFAULT_CONFIG, ...config});

page.on('request', interceptedRequest => {
if (request.isInterceptResolutionHandled()) return;
if (
interceptedRequest.url().endsWith('.png') ||
interceptedRequest.url().endsWith('.jpg')
) {
interceptedRequest.abort('failed', _config.abortPriority);
} else {
// Here we use a custom-configured priority to allow for Opinionated
// continuation.
// We would only want to allow this if we had a very clear reason why
// some use cases required Opinionated continuation.
interceptedRequest.continue(
interceptedRequest.continueRequestOverrides(),
_config.continuePriority, // Why would we ever want priority!==0 here?
);
}
});

上述解决方案确保了向后兼容性,同时还允许用户在使用协作拦截模式时调整包在解析链中的重要性。你的软件包将继续按预期工作,直到用户完全升级其代码和所有第三方软件包以使用协作拦截模式。如果任何处理程序或包仍然使用旧模式,你的包仍然可以在旧模式下运行。

¥The above solutions ensure backward compatibility while also allowing the user to adjust the importance of your package in the resolution chain when Cooperative Intercept Mode is being used. Your package continues to work as expected until the user has fully upgraded their code and all third party packages to use Cooperative Intercept Mode. If any handler or package still uses Legacy Mode, your package can still operate in Legacy Mode too.