Heroku部署无数据库Express服务跨域POST请求返回503 Service Unavailable错误
问题根因定位
你遇到的H13错误是Heroku特有的错误码,含义是后端服务在没有返回任何响应的情况下主动关闭了连接,结合你的代码可以定位到几个核心触发点:
- POST请求body解析缺失
绝大多数Express开发者会漏掉这一步:如果你没有提前配置express.json()中间件,所有POST请求的JSON body都会是undefined,你代码里取req.body.value拿到空值,传入fs.createReadStream会直接触发文件读取错误,你的代码没有监听流的错误事件,Node会直接断开连接,触发H13报错。 - 公共CORS Anywhere服务限流
你用的https://corsanywhere.herokuapp.com是公开的免费CORS代理服务,Heroku官方对这个实例做了严格的速率限制,跨Heroku域名的请求大概率会被直接拦截返回503,现在这个公共服务已经不推荐生产环境使用了。 - 文件流缺少错误兜底逻辑
你当前的代码只监听了流的data和end事件,没有监听error事件,只要文件读取/CSV解析出现任何异常,都会直接导致请求无响应断开。 - Heroku临时文件系统限制
Heroku的dyno文件系统是临时的,只有你部署打包时带入的文件是稳定存在的,运行时生成的文件只要dyno重启/休眠就会丢失,如果你要读取的文件是运行时生成的,大概率会出现路径不存在的问题。
修复步骤
- 第一步:在你的Express服务入口添加body解析中间件和CORS配置,完全弃用公共CORS Anywhere服务
注意如果还没安装cors依赖,先执行npm install cors安装即可
const express = require('express') const cors = require('cors') const app = express() // 配置CORS允许跨域请求 app.use(cors()) // 配置JSON body解析 app.use(express.json()) // 后面再接你的路由配置
- 第二步:给POST接口添加错误处理逻辑
app.post("/form", (req, res) => { const { value } = req.body; // 先判空兜底 if (!value) { return res.status(400).send({error: '参数value不能为空'}) } const results = [] // 要读取的文件路径最好先做校验,避免目录遍历漏洞 const targetPath = path.normalize(value) // 先判断文件是否存在 fs.access(targetPath, fs.constants.F_OK, (err) => { if (err) { return res.status(404).send({error: '文件不存在'}) } fs.createReadStream(targetPath) .pipe(csv({})) .on("data", (data) => results.push(data)) .on("end", () => { res.send(mergeData(results)); }) // 新增流错误监听 .on('error', (err) => { res.status(500).send({error: '文件解析失败'}) }) }) });
- 第三步:修改前端请求地址,生产环境直接请求你的Express服务地址,不要再走CORS代理
if (process.env.NODE_ENV === "production") { origin = "https://cool-exercise-server.herokuapp.com/form"; }
- 第四步:如果要读取的文件是动态生成的,建议你把文件存在Heroku的对象存储服务或者第三方云存储里,不要存在本地dyno文件系统。
内容的提问来源于stack exchange,提问作者Avi Cohen
相关产品推荐
相关产品推荐

