我正在为我的网站使用Next.js + react-apollo + apollo-server + express。最近,我添加了cookie身份验证,因此必须在服务器中启用CORS才能使cookie身份验证起作用。但是我看到在服务器端执行时,阿波罗客户端查询会导致http 500状态。在客户端执行时,相同的查询成功解决。我很困惑,因为我实际上希望问题会在客户端发生,因为CORS在此方面会产生更大的影响。我不确定是什么原因引起的,欢迎您提出任何建议!
我本身的错误如下:
ApolloError:网络错误:对https://example.com/graphql/的请求 失败,原因:编写EPROTO 140152723232576:错误:14094410:SSL 例程:ssl3_read_bytes:sslv3警报握手 失败:../ deps / openssl / openssl / ssl / record / rec_layer_s3.c:1544:SSL 警报编号40
at new ApolloError (/src/node_modules/apollo-client/bundle.umd.js:92:26)
at /src/node_modules/apollo-client/bundle.umd.js:1588:34
at /src/node_modules/apollo-client/bundle.umd.js:2008:15
at Set.forEach (<anonymous>)
at /src/node_modules/apollo-client/bundle.umd.js:2006:26
at Map.forEach (<anonymous>)
at QueryManager.broadcastQueries (/src/node_modules/apollo-client/bundle.umd.js:2004:20)
at /src/node_modules/apollo-client/bundle.umd.js:1483:29
at processTicksAndRejections (internal/process/task_queues.js:93:5) {
graphQLErrors: [],
networkError:FetchError:对https://example.com/graphql/的请求 失败,原因:编写EPROTO 140152723232576:错误:14094410:SSL 例程:ssl3_read_bytes:sslv3警报握手 失败:../ deps / openssl / openssl / ssl / record / rec_layer_s3.c:1544:SSL 警报编号40
我正在使用Amazon Cloudfront提供的SSL证书。
这是我的客户代码:
_app.js
:
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
if (pageProps.errorStatusCode && ctx.res) {
ctx.res.statusCode = pageProps.errorStatusCode;
}
}
return { pageProps };
}
render() { //render... }
}
我有一个用于页面查询的HOC:
const withQuery = (Page, query, variables, errorPolicy = 'none') => {
Page.getInitialProps = async ctx => {
const { apolloClient } = ctx;
try {
const { data } = await apolloClient.query({
query,
variables: vars,
errorPolicy
});
return { data };
} catch (error) {
return { errorStatusCode: error.networkError ? '500' : '404' };
}
};
// if (typeof window === 'undefined') { // THIS CODE IS CAUSING THE ISSUE
// return Page;
// }
}
这是我启动apollo客户端的方式:
import withApollo from 'next-with-apollo';
import ApolloClient, { InMemoryCache } from 'apollo-boost';
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import introspectionQueryResultData from '../../fragmentTypes.json';
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData
});
function createClient({ ctx, headers, initialState }) {
return new ApolloClient({
credentials: 'include',
uri: 'some_graphql_url',
cache: new InMemoryCache({ fragmentMatcher }).restore(initialState || {}),
headers
});
}
export default withApollo(createClient, { getDataFromTree: 'ssr' });
这是我的服务器代码:
import cors from 'cors'
const express = require('express')
const { ApolloServer } = require('apollo-server-express')
const { schema } = require('./models')
const server = new ApolloServer({
schema,
})
// required settings to accept cookies
const corsOptions = {
origin: function (origin, callback) {
if (corsWhitelist.indexOf(origin) !== -1 || !origin) {
callback(null, true)
} else {
callback(new Error(`${origin} - not allowed by CORS`))
}
},
credentials: true
}
let app = express()
app.use(cors(corsOptions))
server.applyMiddleware({ app, cors: false })
const serverUrl = `my_server_url`
app.listen({ port }, () => console.log(`? Server ready at ${serverUrl}`))
总结我的发现:
_app.js
呼叫await Component.getInitialProps(ctx)
时。getInitialProps
在withQuery
HOC中定义,在该位置中,查询是通过apolloClient.query
方法执行的。没有CORS,一切都会正常运行。
编辑:我注意到将headers
选项和CORS一起添加到createClient
时,该问题将开始发生。
EDIT2 :即使没有CORS,也会发生错误,将headers
选项添加到创建阿波罗客户端的createClient
就足够了。
答案 0 :(得分:1)
我收到错误的原因是因为我从服务器端(express.js)传递了所有标头,包括host
标头到Amazon Cloudfront。因为我的网站已配置为使用SNI,所以传递正确的服务器名非常重要(此参数用于确定SSL证书,以便从IP地址为哪个虚拟主机提供服务)。碰巧的是,Apollo客户端使用node-fetch
npm包来发出http请求,而HTTP请求又使用了https
Node.js模块。如果存在host
标头,则https
将服务器名设置为host
的值,否则服务器名将获取主机名(mywebsite.com)的值。因此,在我的情况下,服务器名称为bla.bla.elasticbeanstalk.com
,这当然会导致SSL握手错误,因为SSL证书用于mywebsite.com。我写了更多信息here。
答案 1 :(得分:-1)
您的代码不完整
const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');
// Construct a schema, using GraphQL schema language
const typeDefs = gql`
type Query {
hello: String
}
`;
// Provide resolver functions for your schema fields
const resolvers = {
Query: {
hello: () => 'Hello world!',
},
};
const server = new ApolloServer({ typeDefs, resolvers });
const app = express();
server.applyMiddleware({ app });
app.listen({ port: 4000 }, () =>
console.log(`? Server ready at http://localhost:4000${server.graphqlPath}`)
);