加入我们,从10月8日至10日,前往纽约市,了解关于 GraphQL 联邦和 API 平台工程的最新技巧、趋势和新闻。与我们一起参加2024年纽约市的 GraphQL Summit
文档
免费开始

Apollo Server 插件事件参考


所有插件生命周期方法都是async,除了 willResolveFieldschemaDidLoadOrUpdate

本参考描述了您的自定义 Apollo Server 插件可以响应的生命周期事件。

.css-154yii{-webkit-text-decoration:underline;text-decoration:underline;text-decoration-style:dotted;text-decoration-thickness:1.5px;text-decoration-color:var(--chakra-colors-gray-300);text-underline-offset:0.2em;}.css-154yii:hover,.css-154yii[data-hover]{cursor:help;}.chakra-ui-dark .css-154yii:not([data-theme]),[data-theme=dark] .css-154yii:not([data-theme]),.css-154yii[data-theme=dark]{text-decoration-color:var(--chakra-colors-blue-400);} 触发两种事件,插件可以挂钩的: 服务器生命周期事件请求生命周期事件

  • 服务器生命周期事件与 Apollo Server 本身的生命周期相关的较高级别的事件(例如,serverWillStart)。
  • 请求生命周期事件与特定请求的生命周期相关。

在两个例外情况下,Apollo Server 中的所有插件方法都是 async。第一个例外是 willResolveField,它比其他插件方法调用得更加频繁。第二个例外是 schemaDidLoadOrUpdate,那里将该方法设置为 async 会引入关于方法执行的不明确的顺序语义。

服务器生命周期事件

serverWillStart

当 Apollo Server 正在准备开始为 GraphQL 请求提供服务时,会触发 serverWillStart 事件。服务器直到这个异步方法完成才启动。如果它抛出(即,返回的 Promise 被拒绝),则启动失败,您的服务器不会提供服务。这可以帮助您确保在尝试接收请求之前,您的服务器的所有依赖项都已可用。

这个事件取决于哪个 Apollo Server 集成 使用时触发的时间:

  • 如果您使用的是 startStandaloneServer,则在调用 startStandaloneServer 函数并传入您的服务器实例时触发。
  • 在类似 expressMiddleware 的非 serverless 集成中,它在 start() 方法中触发。
  • serverless 集成中,通常在响应第一个 incoming 请求时触发。

例子

const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async serverWillStart() {
console.log('Server starting!');
},
},
],
});
const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async serverWillStart() {
console.log('Server starting!');
},
},
],
});

drainServer

当 Apollo Server 组件开始停止并且执行 ApolloServer.stop() 方法(无论是由您的代码明确调用还是由终止信号处理器之一调用时,ApolloServer.stop()),将触发 drainServer 事件。在 drainServer 事件处理程序运行期间,GraphQL 操作仍然可以成功执行。此钩子允许您停止接受新的连接并关闭现有的连接。Apollo Server有一个内置插件,它使用此事件来排空 Node http.Server

您可以在返回的 serverWillStart 处理程序的客观上定义您的 drainServer 处理程序,因为这两个处理器通常将交互的数据。目前,drainServer 处理器不接收参数(这可能会在未来改变)。

例子

const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async serverWillStart() {
return {
async drainServer() {
await myCustomServer.drain();
},
};
},
},
],
});
const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async serverWillStart() {
return {
async drainServer() {
await myCustomServer.drain();
},
};
},
},
],
});

serverWillStop

The serverWillStop 事件在 Apollo Server 开始关闭时触发,这是因为调用了 ApolloServer.stop()(可能是您的代码显式调用的,也可能是终止信号处理器之一调用的)。如果您的插件正在运行任何后台任务,这是一个关闭它们的良好时机。

您应在 serverWillStart 处理器返回的对象中定义您的 serverWillStop 处理器,因为这两个处理器通常与相同的数据交互。目前,serverWillStop 处理器不接收参数(这可能在将来变化)。

当您的 serverWillStop 处理器被调用时,Apollo Server 处于一个不再开始执行新的 GraphQL 操作的状态,因此这是一个刷新可观察数据的好地方。如果您正在寻找一个在操作还可以执行时运行的钩子,请尝试 drainServer

例子

const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async serverWillStart() {
const interval = setInterval(doSomethingPeriodically, 1000);
return {
async serverWillStop() {
clearInterval(interval);
},
};
},
},
],
});
const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async serverWillStart() {
const interval = setInterval(doSomethingPeriodically, 1000);
return {
async serverWillStop() {
clearInterval(interval);
},
};
},
},
],
});

renderLandingPage

此事件允许您通过 Apollo Server 的基本 URL 提供自定义登录页面。Apollo Server 在所有 serverWillStart 事件运行后由 Apollo Server 一次性触发。最多只能有一个已安装的插件定义一个 renderLandingPage 处理器。否则,Apollo Server 在启动时将抛出错误。

您应在 serverWillStart 处理器返回的对象中定义您的插件 renderLandingPage 处理器,这使其能够读取传递给 serverWillStart 的值:

index.ts
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
{
async serverWillStart() {
return {
async renderLandingPage() {
const html = `
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<h1>Hello world!</h1>
</body>
</html>`;
return { html };
},
};
},
},
],
});
index.js
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
{
async serverWillStart() {
return {
async renderLandingPage() {
const html = `
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<h1>Hello world!</h1>
</body>
</html>`;
return { html };
},
};
},
},
],
});

该处理器应该返回一个包含具有字符串 html 属性的对象。该属性的值作为任何带有 accept: text/html 头的请求的 HTML 提供。该 html 属性也可以是一个返回字符串的 async 函数。此函数为每个登录页面请求调用。

有关更多登录页面选项,请参阅 更改登录页面

例子

const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async serverWillStart() {
return {
async renderLandingPage() {
return {
async html() {
return `<html><body>Welcome to your server!</body></html>`;
},
};
},
};
},
},
],
});
const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async serverWillStart() {
return {
async renderLandingPage() {
return {
async html() {
return `<html><body>Welcome to your server!</body></html>`;
},
};
},
};
},
},
],
});

requestDidStart

新特性:Apollo Server 4: 在 Apollo Server 4 中,requestDidStart 钩子在并行而不是串行中调用。

每当 Apollo Server 开始满足 GraphQL 请求时,都会触发 requestDidStart 事件。

requestDidStart?(
requestContext: GraphQLRequestContext<TContext>,
): Promise<GraphQLRequestListener<TContext> | void>;

此函数可以选择返回一个包含处理可能跟随 requestDidStart 的事件的函数的对象。

const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async requestDidStart(requestContext) {
// Within this returned object, define functions that respond
// to request-specific lifecycle events.
return {
// The `parsingDidStart` request lifecycle event fires
// when parsing begins. The event is scoped within an
// associated `requestDidStart` server lifecycle event.
async parsingDidStart(requestContext) {
console.log('Parsing started!');
},
};
},
},
],
});
const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async requestDidStart(requestContext) {
// Within this returned object, define functions that respond
// to request-specific lifecycle events.
return {
// The `parsingDidStart` request lifecycle event fires
// when parsing begins. The event is scoped within an
// associated `requestDidStart` server lifecycle event.
async parsingDidStart(requestContext) {
console.log('Parsing started!');
},
};
},
},
],
});

如果您的插件不需要响应任何请求生命周期事件,requestDidStart则不应返回值。

schemaDidLoadOrUpdate

每当Apollo Server首次加载模式或更新模式时,schemaDidLoadOrUpdate事件都会触发。

schemaDidLoadOrUpdate处理函数将获得新的和可选的新核心模式(如果使用网关)。

schemaDidLoadOrUpdate是一个同步插件API(即,它不返回一个Promise)。

例子

const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async serverWillStart() {
return {
schemaDidLoadOrUpdate({ apiSchema, coreSupergraphSdl }) {
console.log(`The API schema is ${printSchema(apiSchema)}`);
if (coreSupergraphSdl) {
console.log(`The core schema is ${coreSupergraphSdl}`);
}
},
};
},
},
],
});
const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async serverWillStart() {
return {
schemaDidLoadOrUpdate({ apiSchema, coreSupergraphSdl }) {
console.log(`The API schema is ${printSchema(apiSchema)}`);
if (coreSupergraphSdl) {
console.log(`The core schema is ${coreSupergraphSdl}`);
}
},
};
},
},
],
});

startupDidFail

如果您的服务器启动失败,则会触发startupDidFail钩子。这可能发生在方案加载失败或serverWillStartrenderLandingPage钩子引发错误的情况下。此钩子接收抛出的error,这是await server.start()引发的相同错误。

例子

const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async startupDidFail({ error }) {
console.log(`Startup failed: ${error}`);
},
},
],
});
const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async startupDidFail({ error }) {
console.log(`Startup failed: ${error}`);
},
},
],
});

请求生命周期事件

如果您使用TypeScript创建插件,请实现 GraphQLRequestListener接口以定义请求生命周期事件的函数。

Apollo Server处理请求时,会按列出的顺序触发这些事件(除了didEncounterErrors,它可能在几个地点触发,具体取决于错误发生的时间)。查看流程图

请注意,并非每个事件都会在每次请求时触发(例如,parsingDidStart对于Apollo Server已缓存并不需要再次解析的操作不会触发)。

didResolveSource

didResolveSource事件在Apollo Server确定要处理的String表示的传入操作后触发。

在这个阶段,不能保证该操作不是格式不正确的。

didResolveSource?(
requestContext: WithRequired<
GraphQLRequestContext<TContext>, 'source' | 'queryHash'>,
>,
): Promise<void>;

parsingDidStart

每当Apollo Server将GraphQL请求解析为关联的AST(抽象语法树)document时,就会触发parsingDidStart事件。

如果 Apollo Server 收到一个查询字符串请求,且该字符串与之前的请求匹配,则相关的文档可能已经存在于 Apollo Server 缓存中。在这种情况下,对于该请求不会调用parsingDidStart,因为不会进行解析。

parsingDidStart?(
requestContext: WithRequired<
GraphQLRequestContext<TContext>, 'source' | 'queryHash'
>,
): Promise<void | (err?: Error) => Promise<void>>;

validationDidStart

每当 Apollo Server 需要验证请求的文档AST与您的时,就会触发validationDidStart事件。

parsingDidStart事件一样,如果请求的文档已经存在于 Apollo Server 缓存中(只有成功验证的文档才会被 Apollo Server 缓存),那么该事件也不会触发。

在这个阶段,文档AST确保是可用的,因为解析必须成功才能进行验证。

validationDidStart?(
requestContext: WithRequired<
GraphQLRequestContext<TContext>,
'source' | 'queryHash' | 'document'
>,
): Promise<void | (err?: ReadonlyArray<Error>) => Promise<void>>;

didResolveOperation

graphql库在请求的文档AST中成功确定要执行的 operation之后,就会触发didResolveOperation事件。在这个阶段,operationName字符串和operationAST都是可用的。

此事件与您的解析器无关。当此事件触发时,您的尚未执行(它们将在executionDidStart之后执行)。

如果 operation是匿名的(即,操作是query { ... }而不是query NamedQuery { ... }),则operationNamenull

如果didResolveOperation钩子抛出GraphQLError,则该错误将被序列化,并以 HTTP 状态码 500 返回给客户端,除非它指定了不同的状态码。

由于可以访问解析和验证后的 operation以及请求特定上下文(例如,contextValue),didResolveOperation钩子是一个执行额外验证的绝佳位置。多个插件可以并行运行didResolveOperation,但如果多个插件抛出错误,客户端只能收到一个错误。

didResolveOperation?(
requestContext: WithRequired<
GraphQLRequestContext<TContext>,
'source' | 'queryHash' | 'document' | 'operationName'
>,
): Promise<void>;

responseForOperation

在执行 GraphQL 操作之前,会立即触发 responseForOperation 事件。如果其返回值解析为非空 GraphQLResponse,则使用该结果而不是执行查询。来自不同插件的钩子会按顺序调用,并使用第一个非空响应。

responseForOperation?(
requestContext: WithRequired<
GraphQLRequestContext<TContext>,
'source' | 'queryHash' | 'document' | 'operationName' | 'operation'
): Promise<GraphQLResponse | null>;

执行开始

每当 Apollo Server 开始执行请求中指定 document AST 的 GraphQL 操作时,executionDidStart 事件都会触发。

executionDidStart?(
requestContext: WithRequired<
GraphQLRequestContext<TContext>,
'source' | 'queryHash' | 'document' | 'operationName' | 'operation'
>,
): Promise<GraphQLRequestExecutionListener | void>;

executionDidStart 可能返回一个具有一个或两个方法的对象 executionDidEndwillResolveFieldexecutionDidEnd 类似于结束钩子:在执行后调用,并带有任何发生的错误。(如果操作使用 增量交付 指令如 @defer,则 executionDidEnd 在所需填充 初始 有效载荷的字段执行完毕时被调用;您可以使用 willSendSubsequentPayload 来连接到各个后续有效载荷的执行结束。) willResolveField 内容见下一节。

将要解析字段

每当 Apollo Server 在执行操作期间即将解析一个单独的字段时,将触发 willResolveField 事件。处理程序传递一个具有四个字段的对象(sourceargscontextValueinfo),它们与传递到解析器中的四个位置参数对应。

您在您的 executionDidStart 处理程序返回的对象中提供您的 willResolveField 处理程序。

您的 willResolveField 处理程序可以可选地返回一个会在解析器结果(或抛出的错误)上被调用的 “结束钩子” 函数。当您的解析器完全解析时,会调用结束钩子(例如,如果解析器返回一个 Promise,钩子会与 Promise 的最终解析结果一起被调用)。

willResolveField 和其结束钩子是同步插件 API(即,它们不返回 Promise)。

willResolveField 仅在 Apollo Server 本身内部解析字段时触发;如果服务器是网关,则不会触发。

例子

const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async requestDidStart(initialRequestContext) {
return {
async executionDidStart(executionRequestContext) {
return {
willResolveField({ source, args, contextValue, info }) {
const start = process.hrtime.bigint();
return (error, result) => {
const end = process.hrtime.bigint();
console.log(`Field ${info.parentType.name}.${info.fieldName} took ${end - start}ns`);
if (error) {
console.log(`It failed with ${error}`);
} else {
console.log(`It returned ${result}`);
}
};
},
};
},
};
},
},
],
});
const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async requestDidStart(initialRequestContext) {
return {
async executionDidStart(executionRequestContext) {
return {
willResolveField({ source, args, contextValue, info }) {
const start = process.hrtime.bigint();
return (error, result) => {
const end = process.hrtime.bigint();
console.log(`Field ${info.parentType.name}.${info.fieldName} took ${end - start}ns`);
if (error) {
console.log(`It failed with ${error}`);
} else {
console.log(`It returned ${result}`);
}
};
},
};
},
};
},
},
],
});

didEncounterErrors

当 Apollo Server 解析、验证或执行 GraphQL 操作时遇到错误时,会触发 didEncounterErrors 事件。错误可以在 requestContext.errors 中访问。

(如果操作使用增量交付指令,如 增量交付@deferdidEncounterErrors 仅在遇到将发送在初始有效载荷中的错误时调用;您可以使用 didEncounterSubsequentErrors 来查找是否有更多错误。)

didEncounterErrors?(
requestContext: WithRequired<
GraphQLRequestContext<TContext>, 'errors'
>,
): Promise<void>;

didEncounterSubsequentErrors

仅当操作使用增量交付指令,如 增量交付@defer 时,《code class="css-1lvdtfu">didEncounterSubsequentErrors 事件才被触发。此钩子在初始有效载荷发送后遇到任何执行错误时被调用;在这种情况下不会调用 didEncounterErrors。相关的错误作为钩子的第二个参数提供(而不是作为 requestContext.errors,这将继续作为初始有效载荷中发送的错误列表)。

didEncounterSubsequentErrors?(
requestContext: GraphQLRequestContextDidEncounterSubsequentErrors<TContext>,
errors: ReadonlyArray<GraphQLError>,
): Promise<void>;

willSendResponse

每当 Apollo Server 即将发送 GraphQL 操作的响应时,就会触发 willSendResponse 事件。即使 GraphQL 操作遇到一个或多个错误,此事件也会触发(并且 Apollo Server 会发送响应)。

(如果操作使用增量交付指令,如 增量交付@deferwillSendResponse 会在发送初始有效载荷之前调用;您可以使用 willSendSubsequentPayload 来找到更多有效载荷将发送的时间和。

willSendResponse 事件还可以在原地修改 GraphQL 响应。例如,您可以向 extensions 对象添加值或在 data 块中删除信息。如果需要修改错误响应,请参阅 formatError 钩子

willSendResponse?(
requestContext: WithRequired<
GraphQLRequestContext<TContext>, 'source' | 'queryHash'
>,
): Promise<void>;

willSendSubsequentPayload

仅针对willSendSubsequentPayload事件在操作中使用增量交付指令(如@defer)时触发。类似于willSendResponse,该钩子在发送初始负载之后,每个负载之前被调用。相关的负载作为钩子的第二个参数提供(不是requestContext上)。如果是最后一个负载,payload.hasNext将返回false。请注意,payload的确切格式由graphql-js项目决定,且截至2022年9月,增量交付支持尚未作为graphql-js的官方版本发布。当官方发布(预计为graphql@17)时,此参数的格式可能变化;在这种情况下,Apollo Server可能在Apollo Server的一次小版本更新中向后不兼容地更改此钩子的详细信息。(目前,只有您安装graphql@17的预发布版本时,此钩子才能被调用。)

willSendSubsequentPayload?(
requestContext: GraphQLRequestContextWillSendSubsequentPayload<TContext>,
payload: GraphQLExperimentalFormattedSubsequentIncrementalExecutionResult,
): Promise<void>;

contextCreationDidFail

当用户提供的context函数抛出错误时,会触发contextCreationDidFail事件。由于请求上下文不完整,此钩子仅接收error(而不是完整的GraphQLRequestContext)。如果您想了解更多关于触发该事件请求的信息,可以考虑将您的context函数包装在try/catch块中,并向抛出的错误添加适当的信息。

例子

const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async contextCreationDidFail({ error }) {
console.log(`Context creation failed: ${error}`);
},
},
],
});
const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async contextCreationDidFail({ error }) {
console.log(`Context creation failed: ${error}`);
},
},
],
});

invalidRequestWasReceived

在请求执行过程中,任何时间抛出“请求错误”都会触发invalidRequestWasReceived事件。这包括CSRF预防和不规范请求(例如,不正确的标头、无效的JSON主体,或无效的GET搜索参数),但不包括不规范的GraphQL。由于请求上下文不完整,此钩子仅接收error(而不是完整的GraphQLRequestContext)。如果您想了解更多关于触发该事件请求的信息,可以考虑将您的context函数包装在try/catch块中,并向抛出的错误添加适当的信息。

例子

const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async invalidRequestWasReceived({ error }) {
console.log(`Bad request: ${error}`);
},
},
],
});
const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async invalidRequestWasReceived({ error }) {
console.log(`Bad request: ${error}`);
},
},
],
});

unexpectedErrorProcessingRequest

在请求执行期间抛出“意外”错误时会触发unexpectedErrorProcessingRequest事件。“意外”错误不包括常见的客户端数据错误,例如验证、解析或GraphQL执行错误。相反,意外错误表明存在编程错误,例如插件钩子意外抛出或Apollo Server遇到错误。无论原因如何,Apollo Server都会将这些类型的错误从客户端掩蔽。

请注意此钩子位于插件的最高级别,而不是位于requestDidStart返回的对象中,因为如果requestDidStart抛出异常,此钩子会触发。

例子

const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async unexpectedErrorProcessingRequest({ requestContext, error }) {
console.log(`Something went wrong: ${error}`);
},
},
],
});
const server = new ApolloServer({
/* ... other necessary configuration ... */
plugins: [
{
async unexpectedErrorProcessingRequest({ requestContext, error }) {
console.log(`Something went wrong: ${error}`);
},
},
],
});
上一页
创建插件
下一页
设置
评分文章评分在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,经营名称为 Apollo GraphQL。

隐私政策

公司