Apollo Server 插件事件参考
所有插件生命周期方法都是async
,除了 willResolveField
和 schemaDidLoadOrUpdate
本参考描述了您的自定义 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 触发两种事件,插件可以挂钩的: 服务器生命周期事件 和 请求生命周期事件。
- 服务器生命周期事件与 Apollo Server 本身的生命周期相关的较高级别的事件(例如,
serverWillStart
)。 - 请求生命周期事件与特定请求的生命周期相关。
- 您可以在对
requestDidStart
事件响应中定义对这些事件的响应,如 响应请求生命周期事件 中所述。
- 您可以在对
在两个例外情况下,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
的值:
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 };},};},},],});
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处理函数将获得新的API模式和可选的新核心模式(如果使用网关)。
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
钩子。这可能发生在方案加载失败或serverWillStart
或renderLandingPage
钩子引发错误的情况下。此钩子接收抛出的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与您的GraphQL模式时,就会触发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
字符串和operation
AST都是可用的。
此事件与您的GraphQL服务器的解析器无关。当此事件触发时,您的解析器尚未执行(它们将在executionDidStart
之后执行)。
如果 operation是匿名的(即,操作是query { ... }而不是query NamedQuery { ... }),则operationName
是null
。
如果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
可能返回一个具有一个或两个方法的对象 executionDidEnd
和 willResolveField
。 executionDidEnd
类似于结束钩子:在执行后调用,并带有任何发生的错误。(如果操作使用 增量交付 指令如 @defer
,则 executionDidEnd
在所需填充 初始 有效载荷的字段执行完毕时被调用;您可以使用 willSendSubsequentPayload
来连接到各个后续有效载荷的执行结束。) willResolveField
内容见下一节。
将要解析字段
每当 Apollo Server
在执行操作期间即将解析一个单独的字段时,将触发 willResolveField
事件。处理程序传递一个具有四个字段的对象(source
、args
、contextValue
和 info
),它们与传递到解析器中的四个位置参数对应。
您在您的 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
中访问。
(如果操作使用增量交付指令,如 增量交付 和 @defer
,didEncounterErrors
仅在遇到将发送在初始有效载荷中的错误时调用;您可以使用 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 会发送响应)。
(如果操作使用增量交付指令,如 增量交付 和 @defer
,willSendResponse
会在发送初始有效载荷之前调用;您可以使用 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}`);},},],});