NodeJS注册跳转图书页报500错误ERR_HTTP_HEADERS_SENT
问题原因
报错Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client的本质是单次HTTP请求被返回了多次响应。
看你写的addUser控制器逻辑:
- 表单校验通过、邮箱已被注册时,调用
res.render返回注册错误页,这一步已经把响应头发给客户端,完成了响应 - 表单校验通过、邮箱未注册时,调用
res.cookie().redirect()跳转到图书页,这一步也已经把响应头发给客户端,完成了响应 - 但这两个分支都没有终止函数执行,代码会直接跳出if判断,执行块末尾的
res.render,尝试第二次给客户端返回响应。此时响应早就发送完毕,自然触发报错。
你观察到跳转逻辑能正常执行,是因为第一次的重定向响应已经被浏览器接收处理,后面第二次返回响应的操作只会在服务端抛错,最终给客户端返回500状态码。
修复方案
核心原则是每个请求的所有逻辑分支只能返回一次响应,返回响应后立刻终止后续代码执行,有两种成熟改法:
方法1:给所有提前返回的响应加return阻断执行
在所有调用res.render/res.redirect的语句前加return,执行完响应就直接退出函数,不会走到后面的重复响应逻辑,修改后的控制器代码如下:
exports.addUser = async (req, res) => { const formData = validateRegisterForm(req.body); if (formData.valid) { const check = await authService.checkIfExists(formData.email.value); if (check) { // 加return,返回响应后直接终止函数 return res.render('register', { title: 'books', formData, errMsg: { valid: true, msg: 'User already exists' } }); } let input = { name: formData.name.value, email: formData.email.value, password: await authService.cryptPassword(formData.psw.value) }; await authService.addUser(input); let user = await authService.getByEmail(input.email); const token = jwtHelper.generateAccessToken(user); delete(user.password); // 加return,重定向后直接终止函数 return res.cookie('authorization', 'Bearer '+token).redirect('/api/books'); } // 表单校验不通过时走这里,直接返回即可 res.render('register', { title: 'books', formData: formData, errMsg: { valid: false } }); }
方法2:用if-else结构做分支互斥
把表单校验不通过的逻辑放到else块中,保证同一时间只会进入一个响应分支,不会出现重复执行的问题:
exports.addUser = async (req, res) => { const formData = validateRegisterForm(req.body); if (formData.valid) { const check = await authService.checkIfExists(formData.email.value); if (check) { res.render('register', { title: 'books', formData, errMsg: { valid: true, msg: 'User already exists' } }); } else { let input = { name: formData.name.value, email: formData.email.value, password: await authService.cryptPassword(formData.psw.value) }; await authService.addUser(input); let user = await authService.getByEmail(input.email); const token = jwtHelper.generateAccessToken(user); delete(user.password); res.cookie('authorization', 'Bearer '+token).redirect('/api/books'); } } else { // 校验不通过逻辑放到else块,和上面的分支完全互斥 res.render('register', { title: 'books', formData: formData, errMsg: { valid: false } }); } }
日常开发更推荐第一种加return的写法,逻辑更直观,后续加新分支也不容易漏写判断导致重复响应的问题。
额外提示:你贴的控制器代码中使用了
authService但没有在文件头部引入,如果运行时出现authService is not defined的报错,记得补上对应引入语句,引入路径按你项目的实际目录结构调整即可。
内容的提问来源于stack exchange,提问作者Ditlir Emush
相关产品推荐
相关产品推荐

