使用cors进行服务器端apollo客户端查询时,为什么会出现500 http错误?

时间:2020-02-19 15:04:57

标签: javascript ssl next.js apollo-client

我正在为我的网站使用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}`))

总结我的发现:

  1. 该问题起源于_app.js呼叫await Component.getInitialProps(ctx)时。
  2. getInitialPropswithQuery HOC中定义,在该位置中,查询是通过apolloClient.query方法执行的。

没有CORS,一切都会正常运行。

编辑:我注意到将headers选项和CORS一起添加到createClient时,该问题将开始发生。

EDIT2 :即使没有CORS,也会发生错误,将headers选项添加到创建阿波罗客户端的createClient就足够了。

2 个答案:

答案 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}`)
);