我尝试将图像上传到 Cloudflare R2 存储桶。但是当我在客户端上传文件时,该文件成功到达网关。完成此步骤后,它不会转到子图并在 devtools 上给出此错误;
{
"errors": [
{
"message": "This operation has been blocked as a potential Cross-Site Request Forgery (CSRF). Please either specify a 'content-type' header (with a type that is not one of application/x-www-form-urlencoded, multipart/form-data, text/plain) or provide a non-empty value for one of the following headers: x-apollo-operation-name, apollo-require-preflight\n",
"extensions": {
"code": "BAD_REQUEST",
"stacktrace": [
"GraphQLError: This operation has been blocked as a potential Cross-Site Request Forgery (CSRF). Please either specify a 'content-type' header (with a type that is not one of application/x-www-form-urlencoded, multipart/form-data, text/plain) or provide a non-empty value for one of the following headers: x-apollo-operation-name, apollo-require-preflight",
"",
],
"serviceName": "userService"
}
}
],
"data": {
"uploadUserPhoto": null
}
}
我将
apollo-require-preflight:true
添加到客户端的标题中。但是我不太确定在 Gateway 和 Subgraphs 之间添加相同的标头。
import { gql, useMutation } from '@apollo/client';
const UPLOAD = gql`
mutation UploadUserPhoto($photo: Upload) {
uploadUserPhoto(photo: $photo)
}
`;
function FileUploadSingle() {
const [upload] = useMutation(UPLOAD);
const handleFileUpload = async (event: any) => {
const file = event.target.files[0];
try {
await upload({
variables: {
photo: file,
},
});
} catch (error) {
console.error(error);
}
};
return (
<div>
<input type="file" onChange={(e: any) => handleFileUpload(e)} />
</div>
);
}
export default FileUploadSingle;
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { createUploadLink } from 'apollo-upload-client';
import {
ApolloClient,
InMemoryCache,
ApolloProvider,
from,
} from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
const uploadLink = createUploadLink({
uri: 'http://localhost:4000/graphql',
headers: { 'apollo-require-preflight': 'true' },
});
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('token');
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
},
};
});
const client = new ApolloClient({
link: from([authLink, uploadLink]),
cache: new InMemoryCache(),
});
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</React.StrictMode>
);
import { getOriginsAccordingToEnvironment } from "./helpers";
import { ApolloGateway, IntrospectAndCompose } from "@apollo/gateway";
import { ApolloServer } from "@apollo/server";
import { expressMiddleware } from "@apollo/server/express4";
import express from "express";
import { json } from "body-parser";
import cors from "cors";
import { ENVIRONMENTS } from "./constants";
import { verifyToken } from "./helpers";
import graphqlUploadExpress from "graphql-upload/graphqlUploadExpress.js";
import waitOn from "wait-on";
require("dotenv").config();
const app = express();
import FileUploadDataSource from "@profusion/apollo-federation-upload";
const startServer = async () => {
const environment = process.env.NODE_ENV as string;
let gateway;
if (environment === ENVIRONMENTS.DEVELOPMENT) {
gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: "userService", url: process.env.USER_SERVICE_URL },
{ name: "paymentService", url: process.env.PAYMENT_SERVICE_URL },
{ name: "productService", url: process.env.PRODUCT_SERVICE_URL },
],
}),
buildService({ name, url }) {
return new FileUploadDataSource({
url,
useChunkedTransfer: true,
willSendRequest({
request,
context,
}: {
request: any;
context: any;
}) {
console.log(request.variables) // THIS GIVES UPLOADED FILE'S INFORMATIONS
request.http.headers.set(
"user",
context.id && context.fullName && context.email && context.isAuth
? JSON.stringify(context)
: null
);
},
});
},
});
}
const server = new ApolloServer({
gateway,
});
await server.start();
app.use(graphqlUploadExpress({ maxFileSize: 10000000, maxFiles: 10 }));
app.use(
"/graphql",
cors({
origin: getOriginsAccordingToEnvironment(environment),
}),
json(),
expressMiddleware(server, {
context: async ({ req }: { req: any }) => {
interface JwtPayload {
id: string;
iat: number;
exp: number;
}
const verifiedToken = verifyToken(
req.headers.authorization
) as JwtPayload;
const user = verifiedToken || null;
return user;
},
})
);
app.listen({ port: 4000 }, () => {
console.log(`🚀 Server ready at http://localhost:4000/graphql`);
});
};
const startGateWayWithAllServices = () => {
const waitOnOptions = {
resources: ["tcp:4001", "tcp:4002", "tcp:4003"],
};
waitOn(waitOnOptions)
.then(() => {
startServer();
})
.catch((err) => {
console.error("ERR:", err);
});
};
startGateWayWithAllServices();
import UserController from "../../controllers/user";
import { IUser } from "../../types";
import { uploadToStorage } from "../../helpers/image-upload";
import { nanoid } from "nanoid";
const resolvers = {
Mutation: {
async uploadUserPhoto(_: any, { photo }: { photo: any }, ctx: any) {
if (!ctx || !ctx.id || !ctx.isAuth) {
throw new Error("You have to login!");
}
const uploadedImage = await uploadToStorage({
filename: `${ctx.id}`,
file: photo,
});
await UserController.updateUserAvatarById({
userId: ctx.id,
avatarURL: uploadedImage.Location,
});
},
},
User: {
async __resolveReference(user: any) {
return await UserController.findUserById({ id: user.id });
},
},
};
export { resolvers };
require("dotenv").config();
import { ApolloServer } from "@apollo/server";
import { buildSubgraphSchema } from "@apollo/subgraph";
import gql from "graphql-tag";
import { resolvers } from "./graphql/resolvers/user";
import { startStandaloneServer } from "@apollo/server/standalone";
import { readFileSync } from "fs";
const mongoose = require("mongoose");
const typeDefs = gql(
readFileSync(`${__dirname}/graphql/types/user.graphql`).toString("utf-8")
);
import { DB_URI } from "../database-config";
const createDatabaseConnection = async () => {
try {
mongoose.set("strictQuery", false);
await mongoose.connect(DB_URI);
console.log("✅ User Service: Connected to DB");
} catch (error) {
console.log("⛔", error);
}
};
const startUserServiceServer = async () => {
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers }),
});
await createDatabaseConnection();
const { url } = await startStandaloneServer(server, {
context: async ({ req }: { req: any }) => {
const user = req.headers.user ? JSON.parse(req.headers.user) : null;
return user;
},
listen: { port: 4001 },
});
console.log(`🚀 User Service: Server ready at ${url}`);
};
startUserServiceServer();