【发布时间】:2020-07-24 19:44:47
【问题描述】:
简介
所以,我正在使用 MERN 堆栈(使用 Heroku + Netlify),并且在处理 DELETE 请求的方式上遇到了一些非常奇怪的一致性问题。我在最后尝试了无数的解决方案 三天试图让这个工作,他们都没有工作。很多这些解决方案都有 来自堆栈溢出,所以如果你想引导我到另一个帖子,很可能我已经看过了。我已经搜索了网络的每个部分,发布这篇文章是我最后的手段。
问题
因此,当我发出删除请求时,我收到了常规的 OPTIONS 请求,因为我在请求的自定义标头(“x-auth-token”)中发送了一个令牌。 OPTIONS 请求总是以 204 解析,这意味着一切都应该没问题。但是,之后,就没有应有的 DELETE 请求了。这本质上是我的问题。我检查了我的 Heroku 日志,我只能看到 OPTIONS 请求,没有别的。
不一致?
所以这是我一直很困惑的地方。问题是,有时它确实有效。即使我使用相同的中间件,我在 API 中使用的其他路由(例如登录和创建新帖子)也可以正常工作。 每次它工作时,我都会收到 OPTIONS 请求,然后是 DELETE 请求(状态为 200),就像我期望的那样。
如果您想要一个可重现场景的示例:
我在登录并获得有效令牌后创建了 X 个帖子,然后我可以在我的主页上的帖子列表中看到这些帖子呈现。然后,我浏览其中一个帖子并通过单击然后单击确认按钮将其删除。我会自动被重定向到列表中的下一个帖子。我重复这个直到我到达最后一个帖子。我删除了那个帖子,因为没有更多帖子了,我被重定向到帖子列表,它是......不是空的!我尝试删除的最后一个帖子仍然存在。
请记住,所有 DELETE 请求都以完全相同的方式发送,所以我很确定这不是前端问题,因此无需在代码中四处寻找。我已经记录了所有内容并进行了调试,它与我的预期 100% 一致。
(创建帖子不会重定向,而删除帖子会重定向?我看不出这会如何影响 DELETE 请求按照往常发送...尽管可能有解决方案事实。)
我尝试过的解决方案
Cors
首先,您可能已经急于用键盘告诉我这是一个 CORS 问题。我昨天也这么想,但现在我不太确定了。我已经尝试在 CORS 中搞乱所有可能的配置设置以使其正常工作。由于我的两个网站位于不同的域上,因此 CORS 会验证请求。我已经将我的前端网站添加到白名单中,并且所有其他请求都正常通过,所以没有问题。我尝试在配置中添加一个 allowHeaders 选项,但它没有做任何比默认设置更多的事情。我还在配置中的允许方法中添加了“选项”,仍然没有。我也在使用 app.use(cors({config}))。稍后我将包含一些代码以详细了解其中的一些内容。
调试
我基本上已经通过在任何地方插入 console.logs 来测试了,发现中间件、选项路由(我尝试使用相同的路由 url 创建一个选项路由)或原始发布路由都没有在 OPTIONS 请求时执行不会导致 DELETE 请求。
静态服务器
这可能是我缺乏经验的地方(这是我的第一个 Web 项目)。我看到一些解决方案告诉我们需要一个静态服务器。所以我尝试设置一个静态服务器,但我没有看到任何结果。所以我不太确定这完成了什么。
异步和等待
此时我只是在尝试,所以我使所有路由异步,看看它是否会做任何事情。没有。
其他
我还弄乱了环境变量和 dotenv 以及其他我不记得的东西。我想这里的一切应该已经足够了解情况了。
代码
index.js
const express = require('express');
require("dotenv").config({ path: "variables.env" });
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const routes = require("./routes/router");
const cors = require("cors");
const morgan = require('morgan')
const app = express();
const whitelist = [
process.env.ORIGIN
];
app.use(
cors({
origin: function (origin, callback) {
if (whitelist.indexOf(origin) !== -1) {
callback(null, true);
} else {
console.log(origin);
callback(new Error("Not allowed by CORS"));
}
}, //frontend server localhost:3000
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
credentials: true, // enable set cookie
}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(morgan('dev'));
mongoose.connect(process.env.MONGODB_URL, {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true
});
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', () => {
console.log('connected to db');
});
const userSchema = mongoose.Schema({
name: String,
password: String
});
// Routes
// TODO: make seperate routers/routes
app.use("/", routes);
// Serve static assets if in production
if (process.env.NODE_ENV === 'production') {
// Set static folder
app.use(express.static('client/build'));
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
});
}
// TODO: set up custom port in future
app.listen(process.env.PORT, () => console.log(`Server listening at http://localhost:${process.env.PORT}`));
// Callback functions?
路由器.js
const express = require('express');
const router = express.Router();
const Post = require('../models/Post');
const User = require('../models/User');
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const auth = require('../middleware/auth');
const adminAuth = require('../middleware/adminAuth');
const cors = require("cors");
require("dotenv").config({ path: "variables.env" });
// import 'moment'
// second onwards are handlers => triggers like the post body then next() to go to the next handler
router.post('/api/add_post', adminAuth, async (req, res, next) => {
try{
newPost = new Post({
title: req.body.title,
body: req.body.body,
author: req.body.author,
created: req.body.created,
});
const savedPost = await newPost.save();
if (!savedUser) throw Error('Something went wrong saving the post');
res.send(savedPost);
} catch (e) {
res.status(400).json({ msg: e.message });
}
});
router.delete('/api/delete_post/:id', adminAuth, async (req, res, next) => {
// timeout?
// console.log(req.body);
try{
const id = req.params.id;
if(!id) throw Error('Invalid ID');
const post = await Post.findById(id);
if (!post) throw Error('Post doesn\'t exist');
const removed = await post.remove();
if(!removed) throw Error('Problem with deleting the post');
res.status(200).json({ success: true });
} catch(e) {
console.log("Error: ", e.message);
res.status(400).json({ msg: e.message, success: false });
}
});
// TODO : UPDATE for async soon
router.post('/api/update_post', adminAuth, async (req, res, next) => {
const id = req.body._id;
test_post_data = {
title: req.body.title,
body: req.body.body,
author: req.body.author,
modified: req.body.modified,
};
console.log(test_post_data, id);
Post.updateOne({ _id: id }, test_post_data, (err) => {
if(err) return next(err);
return res.status(200);
});
});
router.get('/api/get_posts', async (req, res, next) => {
try{
const posts = await Post.find();
if(!posts) throw Error('Error with fetching the posts')
res.send(posts.reverse());
} catch (e) {
res.status(400).json({ msg: e.message });
}
});
router.get('/api/get_chapter/:id', async (req, res, next) => {
try{
const id = req.params.id;
const post = await Post.findOne({_id: id})
if(!post) throw Error('No post was found')
res.send(post);
} catch(e) {
res.status(400).json({ msg: e.message })
}
});
// User routes
// TODO : make in seperate file
router.post('/api/user/register', async (req, res) => {
const { name, email, password } = req.body;
// Simple validation
if (!name || !email || !password) {
return res.status(400).json({ msg: 'Please enter all fields' });
}
try {
const user = await User.findOne({ email });
if (user) throw Error('User already exists');
const salt = await bcrypt.genSalt(10);
if (!salt) throw Error('Something went wrong with bcrypt');
const hash = await bcrypt.hash(password, salt);
if (!hash) throw Error('Something went wrong hashing the password');
const newUser = new User({
name,
email,
password: hash,
admin: false
});
const savedUser = await newUser.save();
if (!savedUser) throw Error('Something went wrong saving the user');
// TODO : check up on expires stuff : 3600 = 1 hr
const token = jwt.sign({ id: savedUser._id, admin: savedUser.admin }, process.env.JWT_SECRET, {
expiresIn: 3600
});
res.status(200).json({
token,
user: {
id: savedUser.id,
name: savedUser.name,
email: savedUser.email,
admin: savedUser.admin
}
});
} catch (e) {
res.status(400).json({ error: e.message });
}
});
router.post('/api/user/login', async (req, res) => {
const { name, password } = req.body;
// Simple validation
if (!name || !password) {
return res.status(400).json({ msg: 'Please enter all fields' });
}
try {
// Check for existing user
const user = await User.findOne({ name });
if (!user) throw Error('User Does not exist');
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) throw Error('Invalid credentials');
const token = jwt.sign({ id: user._id, admin: user.admin }, process.env.JWT_SECRET, { expiresIn: 3600 });
if (!token) throw Error('Couldnt sign the token');
res.status(200).json({
token,
user: {
id: user._id,
name: user.name,
email: user.email,
admin: user.admin
}
});
} catch (e) {
res.status(400).json({ msg: e.message });
}
});
module.exports = router;
adminAuth.js
const jwt = require('jsonwebtoken')
require("dotenv").config({ path: "variables.env" });
module.exports = (req, res, next) => {
console.log(req.header('x-auth-token'));
const token = req.header('x-auth-token');
// Check for token
if (!token)
return res.status(401).json({ msg: 'No token, authorizaton denied' });
try {
// Verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
console.log('decoded:', decoded);
if(!decoded.admin)
return res.status(401).json({ msg: 'Not an admin, authorization denied' });
// Add user from payload
// console.log('decoded:', decoded);
req.user = decoded;
next();
} catch (e) {
res.status(400).json({ msg: 'Token is not valid' });
}
};
请求示例的链接和 Heroku 日志,因为 Stackoverflow 说它是垃圾邮件: https://gist.github.com/macklinhrw/b2fec97642882ba406c49cce3e195c39
编辑
我将 Chrome 请求和响应标头粘贴到底部的 gist 中,但两者都没有响应数据。
我已经使用它进行了一些调试以检查差异,我发现删除操作最终起作用,红色(已取消)请求具有标头,而非工作是完全空的(填充有“临时标头”) ' 如果这意味着什么)。
我无法将请求标头复制粘贴到正在工作的红色(已取消)请求标头的要点中。但是,我粘贴了我认为可能对 chrome 有用的所有内容,希望对您有所帮助。
另外,当我使用 Chrome 网络工具时,我没有看到任何 DELETE 请求,而我在其他工具上看到了它们。不确定它是否重要,可能只是某个地方的配置选项。
【问题讨论】:
-
如果我们可以从 Chrome 检查器中的网络选项卡中看到返回 OPTIONS 响应的请求的网络跟踪,但没有执行应该在它之后的 DELETE,我们可能可以查看导致浏览器中止 DELETE 操作的 OPTIONS 响应中缺少什么。
-
@jfriend00 好的,我在问题中添加了一些内容并将一些内容粘贴到要点中。我尝试添加您想要的所有内容,但如果缺少任何内容,请告诉我,我会添加更多内容。谢谢!
标签: javascript node.js reactjs express axios