next.js - 无法确定 Next.js 的正确配置,使用 Apollo/GraphQL-Yoga 进行上传和订阅
问题描述
请原谅我的新手问题,但我很难让它正常工作。
我很难让 Next.js、Apollo 和 GraphQL-Yoga 的订阅行为正常。我已经尝试到处寻找答案,并且已经为此工作了大约 2 周,但没有运气。
我的代码从 Wes Bos 的 Advanced GraphQL 课程代码开始,它使用 Next.js、Apollo、Prisma 和 GraphQL-Yoga。它还使用 cookie 来存储 JWT 令牌,我认为这可能会导致 WebSocketLink 身份验证出现问题,但我可能是错的。我已成功将上传添加到 Amazon S3,但我一直在努力让订阅正常工作。我不断收到超时,与 websocket 断开连接,有时我在控制台中看到大量的连接/断开连接消息,这些消息似乎不正确,但我不能 100% 确定会发生什么。我想在我的网站上拥有实时聊天功能以及通知。我以为我在某一时刻完成了所有工作,但它似乎是断断续续的。有时它可以工作并且聊天/通知会立即出现,有时它们不会
无论如何,这是我的代码:
客户pages/_app.js
:
import App, { Container } from "next/app";
import Root from "../components/Root";
import { ApolloProvider } from "@apollo/react-hooks";
import withData from "../lib/withData";
class MyApp extends App {
// gets all page properties and queries before loading to pass along
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
// this exposes the query to the user
pageProps.query = ctx.query;
return { pageProps };
}
render() {
const { Component, apollo, pageProps } = this.props;
return (
<Container>
<ApolloProvider client={apollo}>
<Root client={apollo}>
<Component client={apollo} {...pageProps} />
</Root>
</ApolloProvider>
</Container>
);
}
}
export default withData(MyApp);
客户lib/withData.js
:
import withApollo from "next-with-apollo";
import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { HttpLink } from "apollo-link-http";
import * as ws from "ws";
import { WebSocketLink } from "apollo-link-ws";
import { getMainDefinition } from "apollo-utilities";
import { onError } from "apollo-link-error";
import { ApolloLink, Observable, split } from "apollo-link";
import { RetryLink } from "apollo-link-retry";
import { createUploadLink } from "apollo-upload-client";
import { endpoint, wsEndpoint } from "../config";
const request = (operation, headers) => {
operation.setContext({
fetchOptions: {
credentials: "include"
},
headers
});
};
function createClient({ ctx, headers, initialState }) {
const httpLink = new HttpLink({
uri: process.env.NODE_ENV === "development" ? endpoint : endpoint
});
const wsLink = process.browser
? new WebSocketLink({
uri: process.env.NODE_ENV === "development" ? wsEndpoint : wsEndpoint,
options: {
reconnect: true
}
})
: () => {
console.log("SSR");
};
return new ApolloClient({
link: ApolloLink.from([
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}`);
if (networkError) console.log("[Network error]: ", networkError);
}),
new ApolloLink(
(operation, forward) =>
new Observable(observer => {
let handle;
Promise.resolve(operation)
.then(oper => request(oper, headers))
.then(() => {
handle = forward(operation).subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer)
});
})
.catch(observer.error.bind(observer));
return () => {
if (handle) handle.unsubscribe();
};
})
),
new RetryLink().split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
// httpLink,
// ERROR: httpLink breaks uploader
createUploadLink({
uri: process.env.NODE_ENV === "development" ? endpoint : endpoint
})
)
]),
cache: new InMemoryCache().restore(initialState || {})
});
}
export default withApollo(createClient);
服务器index.js
:
require("dotenv").config({ path: ".env" });
const cookie = require("cookie");
const cookieParser = require("cookie-parser");
const cors = require("cors");
const jwt = require("jsonwebtoken");
const expressip = require("express-ip");
const helmet = require("helmet");
const compression = require("compression");
const { S3 } = require("aws-sdk");
const logger = require("./utils/logger");
const createServer = require("./createServer");
const db = require("./db");
const server = createServer();
const s3client = new S3({
accessKeyId: process.env.S3_KEY,
secretAccessKey: process.env.S3_SECRET,
params: {
Bucket: process.env.S3_BUCKET
}
});
server.express.use(helmet());
server.express.use(compression());
server.express.use(cookieParser());
server.express.use(expressip().getIpInfoMiddleware);
// get the users IP information
server.express.use((req, res, next) => {
const userIp = req.ipInfo;
if (userIp) {
req.userIp = userIp;
}
next();
});
// decode the JWT so we can ge the user ID on each request
server.express.use((req, res, next) => {
const { token } = req.cookies;
if (token) {
const { userId } = jwt.verify(token, process.env.APP_SECRET);
// put the userId onto the request for future requests to access
req.userId = userId;
}
next();
});
// create a middleware that populates the user on each request
server.express.use(async (req, res, next) => {
// if they aren't logged in, skip this
if (!req.userId) return next();
const user = await db.query.user(
{ where: { id: req.userId } },
"{ id, email, emailMask, emailVerified, role, permissions, account { accountType isBanned } }"
);
req.user = user;
next();
});
const corsWhitelist = [
process.env.FRONTEND_URL,
process.env.ADMIN_URL
];
server.express.use(
"/*",
cors({
credentials: true,
origin: function(origin, callback) {
// note: if same origin makes requests with origin header, it needs to be whitelisted
if (corsWhitelist.indexOf(origin) !== -1) {
// console.log("express origin on whitelist");
// console.log("express origin", origin);
callback(null, true);
} else if (origin === undefined) {
// console.log("express origin is undefined");
callback(null, true);
} else {
// callback(null, true)
callback(new Error(`${origin} not allowed by CORS`));
}
}
})
); // allow cors
// if (process.env.NODE_ENV === 'development') server.express.use(logger);
server.start(
{
cors: {
credentials: true,
origin: function(origin, callback) {
// note: if same origin makes requests with origin header, it needs to be whitelisted
if (corsWhitelist.indexOf(origin) !== -1) {
// console.log("gql origin on whitelist");
// console.log("gql origin", origin);
callback(null, true);
} else if (origin === undefined) {
// console.log("gql origin is undefined");
callback(null, true);
} else {
// callback(null, true)
callback(new Error(`${origin} not allowed by CORS`));
}
}
},
subscriptions: {
keepAlive: true,
onConnect: async (connectionParams, webSocket) => {
console.log("Websocket CONNECTED");
const header = webSocket.upgradeReq.headers.cookie;
const { token } = cookie.parse(header);
try {
const promise = new Promise((resolve, reject) => {
const { userId } = jwt.verify(token, process.env.APP_SECRET);
resolve(userId);
});
const user = await promise;
return user;
} catch (err) {
throw new Error(err);
}
},
onDisconnect: () => {
console.log("Websocket DISCONNECTED");
}
}
},
deets => {
console.log(
` Backend is now running on port http:/localhost:${deets.port}`
);
}
);
服务器createServer.js
:
const { GraphQLServer, PubSub } = require("graphql-yoga");
const depthLimit = require("graphql-depth-limit");
const Mutation = require("./resolvers/Mutation");
const Query = require("./resolvers/Query");
const Subscription = require("./resolvers/Subscription");
const Conversation = require("./resolvers/Conversation");
const db = require("./db");
const pubsub = new PubSub();
// Create the GraphQL Yoga Server
function createServer() {
return new GraphQLServer({
typeDefs: "src/schema.graphql",
resolvers: {
Mutation,
Query,
Subscription,
Conversation
},
resolverValidationOptions: {
requireResolversForResolveType: false
},
// https://github.com/stems/graphql-depth-limit
// https://blog.apollographql.com/securing-your-graphql-api-from-malicious-queries-16130a324a6b
validationRules: [depthLimit(10)],
uploads: {
// Limits here should be stricter than config for surrounding
// infrastructure such as Nginx so errors can be handled elegantly by
// graphql-upload:
// https://github.com/jaydenseric/graphql-upload#type-uploadoptions
maxFileSize: 10000000, // 10 MB
maxFiles: 20
},
context: (req, connection) => ({
...req,
pubsub,
db
})
});
}
module.exports = createServer;
我也不断收到WebSocket connection to 'ws://localhost:4444/' failed: WebSocket is closed before the connection is established.
错误,这可能是我的主要问题,但我不知道如何解决或清理它。
任何帮助将不胜感激,因为这非常令人沮丧!
谢谢!
解决方案
推荐阅读
- windows - Windows:网络映射驱动器上的 FlushFileBuffers 系统调用失败
- sql - 显示表返回“显示已处理的表”。
- android - Android:Exoplayer - ExtractorMediaSource 已弃用
- python - 我是 python 新手,我想使用 python 计算 excel 中列的字符串
- excel - 无法通过 Excel-VBA 执行/传递 SQL 代码块到 Oracle
- postgresql - Phoenix Framework:为什么混合 ecto.create 和混合 phx.server 不起作用?
- asp.net-mvc - 未使用 Asp.net MVC 加载数据表
- python - Unicode_literals(来自 __future__)不适用于 openCV 的 VideoWriter() 中的fourcc 编解码器参数
- python - 变换矩阵 - 显示所有 0 值
- android - 是否可以从任何地方通过语音命令触发您的应用程序?