GraphQL(Nodejs+Reactjs)跨域CORS错误排查求助
先看你遇到的错误:
Access to fetch at 'http://localhost:4000/' from origin 'http://localhost:3000/' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
这个错误的核心是服务器没有正确返回CORS允许的响应头,你的配置里有两个关键错误,分别在服务器和客户端代码里,我来帮你逐个修复:
一、服务器端的配置错误
你的GraphQL Server配置里有两处写法错误:
1. 配置项名称写错 + CORS origin格式错误
GraphQLServer的构造函数里,CORS配置应该放在options字段(注意是复数),而不是opt;另外origin的写法错误,你写成了{['http://localhost:3000/']}(一个包含数组的对象),正确的格式应该是字符串或者数组。
2. server.start的参数传递错误
server.start方法不需要单独传opt变量,构造时的配置会自动生效,或者你也可以在这里覆盖配置。
修改后的服务器代码:
import { GraphQLServer } from 'graphql-yoga' import { prisma } from './generated/prisma-client' import resolvers from './resolvers' const server = new GraphQLServer({ typeDefs: './src/schema.graphql', resolvers, context: request => ({ ...request, resolverValidationOptions: { requireResolversForResolveType: false }, prisma, }), // 正确配置CORS:使用options字段,origin设置为允许的域名 options: { cors: { credentials: true, origin: 'http://localhost:3000' // 单个域名用字符串,多个域名可以传数组:['http://localhost:3000', 'https://your-prod-domain.com'] } } }) // start方法直接启动即可,无需额外传配置 server.start(() => console.log(`Server is running on http://localhost:4000`));
二、客户端的配置错误
你在authLink里添加了Access-Control-Allow-*系列请求头,这是完全错误的:
这些
Access-Control-Allow-*头是服务器响应给浏览器的,用来告诉浏览器允许哪些域名、方法访问资源,客户端发送请求时不需要添加这些头,反而会导致预检请求失败。
另外你的ApolloClient链路拼接也有问题,错误处理链路没有正确加入请求链路中。
修改后的客户端代码:
import { ApolloProvider } from 'react-apollo' import ApolloClient from 'apollo-client' import {BrowserRouter} from 'react-router-dom' import { setContext } from 'apollo-link-context' import { AUTH_TOKEN } from './constants' import { createHttpLink } from 'apollo-link-http' import { InMemoryCache } from 'apollo-cache-inmemory'; import history from './history'; import { onError } from "apollo-link-error"; import { SnackbarProvider, useSnackbar } from 'notistack'; const httpLink = createHttpLink({ uri: 'http://localhost:4000/' }); const authLink = setContext((_, { headers }) => { const token = localStorage.getItem(AUTH_TOKEN); return { headers: { // 只保留authorization头,移除所有Access-Control-*的头 authorization: token ? `Bearer ${token}` : '' } } }); const linkError = onError(({ graphQLErrors, networkError }) => { if (graphQLErrors) graphQLErrors.map(({ message, locations, path }) => console.log( `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`, )); if (networkError) console.log(`[Network error]: ${networkError}`); }); // 正确拼接所有链路:auth链路 -> http链路 -> 错误处理链路 const client = new ApolloClient({ link: authLink.concat(httpLink).concat(linkError), cache: new InMemoryCache(), fetchOptions: { mode: 'cors' }, }); ReactDOM.render( <BrowserRouter> <ApolloProvider client={client}> <SnackbarProvider> <App /> </SnackbarProvider> </ApolloProvider> </BrowserRouter>, document.getElementById('root'), );
为什么这样修改能解决问题?
- 服务器端正确配置了
cors.origin,会在响应头里返回Access-Control-Allow-Origin: http://localhost:3000,浏览器就会允许来自localhost:3000的请求。 - 客户端移除了多余的
Access-Control-*请求头,避免了预检请求被服务器拒绝。 - 修复了ApolloClient的链路拼接,确保错误处理逻辑正常生效。
现在重启服务器和客户端,CORS问题应该就能解决了。
内容的提问来源于stack exchange,提问作者Natali Hachok

