— 1 min read
总共就四个文件:
1export { ApolloServer } from "./ApolloServer";2export type { CreateHandlerOptions } from "./ApolloServer";
入口文件, 不做讲解
先去掉了文件上传相关的代码实现
继承了apollo-server
中导出的ApolloServerBase
类, 然后实现了createGraphQLServerOptions
与createHandler
方法, 第一个:
1createGraphQLServerOptions(req: NowRequest, res: NowResponse): Promise<GraphQLOptions> {2 return super.graphQLServerOptions({ req, res });3 }
暂时没搞懂这是用来干啥的, 全局只有这个 文件上传相关的, 暂时跳过.
graphQLServerOptions
这个方法的作用是从对象中生成graphql options
, 对象包括请求(http.IncomingRequests
)以及特定实现(Express/Koa/Hapi/...)的选项, 这里的选项应该是指像Apollo-Server-Koa
中传入的选项那样, 因为这个方法定义在Apollo-Server-Core
里面.
createHandler
, 绝大部分是在处理 cors...
首先生成一个空的 Header 类:
1// Headers 类来自于node-fetch2const corsHeaders = new Headers();
然后判断创建句柄(Handler)时是否传入了cors
选项:
1if (cors) {2 if (cors.methods) {3 // 设置 access-control-allow-methods字段4 }5
6 if (cors.allowedHeaders) {7 // 设置 access-control-allow-headers 字段8 }9
10 if (cors.exposedHeaders) {11 // 设置 access-control-expose-headers 字段12 }13
14 if (cors.credentials) {15 corsHeaders.set(`access-control-allow-credentials`, `true`);16 }17 if (typeof cors.maxAge === `number`) {18 corsHeaders.set(`access-control-max-age`, cors.maxAge.toString());19 }20}
其实这里有个思路, 比如我实现一个Apollo-Server-Koa-Vercel
, 那是不是可以用中间件的形式来配置 cors 就好了.
然后返回一个异步函数, 即Vercel Functions
的规范:
1return async (req: NowRequest, res: NowResponse) => {2 // ...3};
内部:
首先使用上面的 cors 头字段来配置请求头:
1const requestCorsHeaders = new Headers(corsHeaders);
对cors.origin
做处理:
1if (cors && cors.origin) {2 const requestOrigin = req.headers.origin;3 if (typeof cors.origin === `string`) {4 requestCorsHeaders.set(`access-control-allow-origin`, cors.origin);5 } else if (6 requestOrigin &&7 (typeof cors.origin === `boolean` ||8 (Array.isArray(cors.origin) &&9 requestOrigin &&10 cors.origin.includes(requestOrigin as string)))11 ) {12 requestCorsHeaders.set(13 `access-control-allow-origin`,14 requestOrigin as string15 );16 }17
18 const requestAccessControlRequestHeaders =19 req.headers[`access-control-request-headers`];20 if (!cors.allowedHeaders && requestAccessControlRequestHeaders) {21 requestCorsHeaders.set(22 `access-control-allow-headers`,23 requestAccessControlRequestHeaders as string24 );25 }26}
然后将其拼装为对象:
1const requestCorsHeadersObject = Array.from(requestCorsHeaders).reduce<2 Record<string, string>3>((headersObject, [key, value]) => {4 headersObject[key] = value;5 return headersObject;6}, {});
这一部分我的理解是对请求做处理, 这样在前端跨域调用 faas 的时候就不会被拦截了.
然后快速通过 OPTIONS 请求:
1if (req.method === `OPTIONS`) {2 setHeaders(res, requestCorsHeadersObject);3 return res.status(204).send(``);4}
下面还对req.url = '/.well-known/apollo/server-health'
这种情况做了处理, 是 Apollo 的 onHealthCheck 相关的, 也跳过.
在最后一部分是对graphql-playground
的处理:
1if (this.playgroundOptions && req.method === `GET`) {2 const acceptHeader = req.headers.Accept || req.headers.accept;3 if (acceptHeader && acceptHeader.includes(`text/html`)) {4 const path = req.url || `/`;5 const playgroundRenderPageOptions: PlaygroundRenderPageOptions = {6 endpoint: path,7 ...this.playgroundOptions,8 };9
10 setHeaders(res, {11 "Content-Type": `text/html`,12 ...requestCorsHeadersObject,13 });14 return res15 .status(200)16 .send(renderPlaygroundPage(playgroundRenderPageOptions));17 }18}
这里的逻辑主要是判断出本次请求是在请求 playground, 就渲染并返回
1return res.status(200).send(renderPlaygroundPage(playgroundRenderPageOptions));
renderPlaygroundPage
函数来自于@apollographql/graphql-playground-html
包, 应该是类似 express-graphql 中对 graphiql 的处理.
然后在最后, 返回被graphqlVercel
处理过的函数:
1return graphqlVercel(async () => {2 await promiseWillStart;3 return this.createGraphQLServerOptions(req, res);4})(req, res);
然后createHandler
就结束了, 先看一下最终调用方式:
1const server = new ApolloServer({2 typeDefs,3 resolvers,4 playground: true,5 introspection: true,6});7
8export default server.createHandler();
也就是说, 调用 createHandler 方法返回了:
1async (req, res) => {2 // ...3 return graphqlVercel(async () => {4 await promiseWillStart;5 return this.createGraphQLServerOptions(req, res);6 })(req, res);7};
先去看看 graphqlVercel 是个啥:
1export function graphqlVercel(2 options: GraphQLOptions | NowGraphQLOptionsFunction3): NowApiHandler {4 if (!options) throw new Error(`Apollo Server requires options.`);5
6 if (arguments.length > 1) {7 throw new Error(8 `Apollo Server expects exactly one argument, got ${arguments.length}`9 );10 }11
12 const graphqlHandler = async (req: NowRequest, res: NowResponse) => {13 if (req.method === `POST` && !req.body) {14 return res.status(500).send(`POST body missing.`);15 }16
17 try {18 const { graphqlResponse, responseInit } = await runHttpQuery([req, res], {19 method: req.method as string,20 options,21 query: req?.body || req.query,22 request: convertNodeHttpToRequest(req),23 });24 setHeaders(res, responseInit.headers ?? {});25 return res.status(200).send(graphqlResponse);26 } catch (error) {27 const { headers, statusCode, message }: HttpQueryError = error;28 setHeaders(res, headers ?? {});29 return res.status(statusCode).send(message);30 }31 };32
33 return graphqlHandler;34}
graphqlHandler
函数, graphqlVercel
接受的(req, res)
就是给这个函数使用的, 然后内部其实就是调用runHttpQuery
方法, 执行完请求之后就res.status(200).send(graphqlResponse)
进行响应.这里导出了setHeaders
方法, 在预检处理 onHealthCheck 还有 playground 的响应中都进行了调用, 先盲猜一手这个是设置响应头的, 因为调用方式是这样的:
1setHeaders(res, {2 "Content-Type": `text/html`,3 ...requestCorsHeadersObject,4});
这个文件其实也很简单...
1import { NowResponse } from "@vercel/node";2
3export const setHeaders = (4 res: NowResponse,5 headers: Record<string, any>6): void => {7 for (const [name, value] of Object.entries(headers)) {8 res.setHeader(name, value);9 }10};
2333 这个猜不对感觉可以转行了