如何在 GraphQL 中设置 http 状态码
Posted
技术标签:
【中文标题】如何在 GraphQL 中设置 http 状态码【英文标题】:How to set http status code in GraphQL 【发布时间】:2018-10-01 01:07:16 【问题描述】:我想在我的 GraphQL 身份验证查询中设置一个 http 状态代码,具体取决于身份验证尝试是成功 (200)、未经授权 (401) 还是缺少参数 (422)。
我正在使用Koa and Apollo 并且已经像这样配置了我的服务器:
const graphqlKoaMiddleware = graphqlKoa(ctx =>
return (
schema,
formatError: (err) => ( message: err.message, status: err.status ),
context:
stationConnector: new StationConnector(),
passengerTypeConnector: new PassengerTypeConnector(),
authConnector: new AuthConnector(),
cookies: ctx.cookies
)
)
router.post("/graphql", graphqlKoaMiddleware)
如您所见,我已将formatError
设置为返回消息和状态,但目前只返回消息。错误消息来自我在解析器函数中抛出的错误。
例如:
const resolvers =
Query:
me: async (obj, username, password, ctx) =>
try
return await ctx.authConnector.getUser(ctx.cookies)
catch(err)
throw new Error(`Could not get user: $err`);
我对这种方法的唯一问题是它在错误消息中设置状态代码,而不是实际更新响应对象。
即使对于失败的查询/突变,GraphQL 是否也需要 200
响应,或者我可以如何更新响应对象状态代码?如果不是,如何设置上述错误对象状态码?
【问题讨论】:
【参考方案1】:apollo-server-express V3 支持这一点。创建自己的插件。然后您可以查看抛出的错误以确定状态码。
import ApolloServerPlugin from "apollo-server-plugin-base/src/index";
const statusCodePlugin:ApolloServerPlugin =
async requestDidStart(requestContext)
return
async willSendResponse(requestContext)
const errors = (requestContext?.response?.errors || []) as any[];
for(let error of errors)
if(error?.code === 'unauthorized')
requestContext.response.http.status = 401;
if(error?.code === 'access')
requestContext.response.http.status = 403;
,
;
export default statusCodePlugin;
【讨论】:
【参考方案2】:根据 Daniels 的回答,我已经设法编写了中间件。
import HttpQueryError, runHttpQuery from 'apollo-server-core';
import ApolloServer from 'apollo-server-express';
// Source taken from: https://github.com/apollographql/apollo-server/blob/928f70906cb881e85caa2ae0e56d3dac61b20df0/packages/apollo-server-express/src/ApolloServer.ts
// Duplicated apollo-express middleware
export const badRequestToOKMiddleware = (apolloServer: ApolloServer) =>
return async (req, res, next) =>
runHttpQuery([req, res],
method: req.method,
options: await apolloServer.createGraphQLServerOptions(req, res),
query: req.method === 'POST' ? req.body : req.query,
request: req,
).then(
( graphqlResponse, responseInit ) =>
if (responseInit.headers)
for (const [name, value] of Object.entries(responseInit.headers))
res.setHeader(name, value);
res.statusCode = (responseInit as any).status || 200;
// Using `.send` is a best practice for Express, but we also just use
// `.end` for compatibility with `connect`.
if (typeof res.send === 'function')
res.send(graphqlResponse);
else
res.end(graphqlResponse);
,
(error: HttpQueryError) =>
if ('HttpQueryError' !== error.name)
return next(error);
if (error.headers)
for (const [name, value] of Object.entries(error.headers))
res.setHeader(name, value);
res.statusCode = error.message.indexOf('UNAUTHENTICATED') !== -1 ? 200 : error.statusCode;
if (typeof res.send === 'function')
// Using `.send` is a best practice for Express, but we also just use
// `.end` for compatibility with `connect`.
res.send(error.message);
else
res.end(error.message);
,
);
;
app.use(apolloServer.graphqlPath, badRequestToOKMiddleware(apolloServer));
【讨论】:
【参考方案3】:假设您的 err.status 已经是 401 等整数,请尝试添加响应并设置响应状态代码:
const graphqlKoaMiddleware = graphqlKoa(ctx =>
return (
schema,
response: request.resonse,
formatError: (err) =>
response.statusCode = err.status;
return (message: err.message, status: err.status),
context:
stationConnector: new StationConnector(),
passengerTypeConnector: new PassengerTypeConnector(),
authConnector: new AuthConnector(),
cookies: ctx.cookies
))
【讨论】:
【参考方案4】:对于 apollo-server,安装 apollo-server-errors 包。对于身份验证错误,
import AuthenticationError from "apollo-server-errors";
然后,在您的解析器中
throw new AuthenticationError('unknown user');
这将返回 400 状态代码。
在this blog中阅读有关此主题的更多信息
【讨论】:
【参考方案5】:除非 GraphQL 请求本身格式错误,否则 GraphQL 将返回 200 状态代码,即使在其中一个解析器中抛出错误也是如此。这是设计使然,因此实际上没有办法配置 Apollo 服务器来改变这种行为。
也就是说,您可以轻松连接自己的中间件。您可以导入 Apollo 中间件在后台使用的 runHttpQuery
函数。事实上,您几乎可以复制 source code 并修改它以满足您的需要:
const graphqlMiddleware = options =>
return (req, res, next) =>
runHttpQuery([req, res],
method: req.method,
options: options,
query: req.method === 'POST' ? req.body : req.query,
).then((gqlResponse) =>
res.setHeader('Content-Type', 'application/json')
// parse the response for errors and set status code if needed
res.write(gqlResponse)
res.end()
next()
, (error) =>
if ( 'HttpQueryError' !== error.name )
return next(error)
if ( error.headers )
Object.keys(error.headers).forEach((header) =>
res.setHeader(header, error.headers[header])
)
res.statusCode = error.statusCode
res.write(error.message)
res.end()
next(false)
)
【讨论】:
被低估的答案 好的,所以我正在尝试这样做,但我不知道如何将这个东西与 Apollo 挂钩。我们是否有任何示例代码可以指出如何将它们连接在一起,或者作者可以提供一个示例吗?谢谢。 您提供的中间件如何使用?【参考方案6】:如您所见 here formatError
不支持状态代码,您可以做的是创建一个包含消息和状态字段的状态响应类型,并在解析器上返回相应的。
即使查询/突变失败,GraphQL 是否也需要 200 响应?
不,如果查询失败,它将返回 null
以及您在服务器端抛出的错误。
【讨论】:
以上是关于如何在 GraphQL 中设置 http 状态码的主要内容,如果未能解决你的问题,请参考以下文章