Express调用res.render返回JSON响应 未正常渲染EJS页面问题
问题排查思路与解决方案
按优先级排查核心问题
1. 重定向逻辑未终止执行,触发响应头冲突
当前token校验逻辑存在明显流程漏洞:
if (amountOfTokens == undefined || amountOfTokens == null) { res.redirect('/dashboard') } let numOfRecords = body.length res.render('dashboard.ejs', {data: req.data, body: body, amountOfTokens: amountOfTokens, numOfRecords: numOfRecords})
当amountOfTokens为空触发重定向时,代码没有中断执行,会继续往下调用res.render,导致同一个请求连续触发两次响应发送:
- 第一次发送302重定向响应,响应头包含跳转路径
- 第二次尝试发送渲染后的HTML内容
此时Node.js会在控制台抛出ERR_HTTP_HEADERS_SENT警告,部分浏览器、反向代理遇到这种响应头错乱的情况,会自动将响应内容按JSON格式解析,就会出现「展示JSON响应头+页面路径」的现象。
修复方式:重定向后加return终止后续逻辑执行
if (amountOfTokens == undefined || amountOfTokens == null) { return res.redirect('/dashboard') }
2. 响应头Content-Type被前置逻辑意外覆盖
res.render默认会自动将响应头设置为text/html; charset=utf-8,如果最终返回的Content-Type是application/json,说明执行res.render之前已经有逻辑修改了响应头:
- 检查全局中间件:是否在路由注册前写了统一设置响应类型为json的逻辑,比如全局拦截器里固定写了
res.setHeader('Content-Type', 'application/json')的配置 - 检查
authCheck鉴权中间件:是否在鉴权逻辑里,针对未登录/异常场景提前设置了JSON响应头,甚至提前调用res.json()返回数据,但没有加return终止后续路由逻辑执行 - 检查自定义响应封装方法:如果给res对象挂载了统一返回JSON的工具方法,确认是否在该路由逻辑里被意外触发
快速排查方式:在res.render调用前打印当前已设置的响应头
console.log('当前响应类型', res.get('Content-Type')) let numOfRecords = body.length res.render('dashboard.ejs', {data: req.data, body: body, amountOfTokens: amountOfTokens, numOfRecords: numOfRecords})
如果打印结果已经是application/json,顺着调用栈往上找到修改响应头的逻辑调整即可,也可以在render前手动强制设置响应类型覆盖:
res.type('html') res.render('dashboard.ejs', /** 传参 */)
3. 请求方式与渲染场景不匹配
如果/findRecord接口是被前端通过axios/fetch等AJAX工具异步调用的,而不是浏览器地址栏直接访问、普通a标签跳转、表单提交触发的:
- 哪怕后端正常返回HTML内容,浏览器也不会自动跳转渲染页面,只会把HTML内容作为响应结果交给JS逻辑处理,部分AJAX库默认会把响应当做JSON格式解析,就会出现解析失败、直接展示响应原始内容的情况
修复方式:如果是需要浏览器渲染整页的场景,用普通页面跳转方式访问该路由,不要用AJAX异步请求。
4. 下游接口异常未做拦截
当前调用https://myurl.com/user_records接口时,没有判断HTTP状态码,只要request库没有抛出系统级错误(比如网络不通)就会往下走render逻辑。如果下游接口返回4xx/5xx的JSON格式错误(比如鉴权失败、参数错误),直接把错误JSON传给模板,若模板里有直接输出原始数据的逻辑,也会出现类似返回JSON的现象。
修复方式:增加下游接口状态校验,异常场景单独处理
request.post({ url: 'https://myurl.com/user_records', headers: { "Cookie": req.headers?.cookie, }, json: { max_date: new Date().valueOf() } }, function(err, httpResponse, body) { if (err) { console.log(err) return res.status(500).send('服务异常') } // 新增下游接口状态校验 if (httpResponse.statusCode !== 200) { console.log('下游接口返回异常', httpResponse.statusCode, body) return res.redirect('/dashboard') } if (amountOfTokens == undefined || amountOfTokens == null) { return res.redirect('/dashboard') } let numOfRecords = body.length res.render('dashboard.ejs', {data: req.data, body: body, amountOfTokens: amountOfTokens, numOfRecords: numOfRecords}) })
额外优化点
给request.post回调加的async关键字没有实际作用——回调内部没有任何await异步逻辑,反而可能因为request库对async回调的兼容问题导致意外流程错误,可以直接删掉。
内容的提问来源于stack exchange,提问作者devdude19289
相关产品推荐
相关产品推荐

