Heroku Backend 无法接收 fetch POST 请求
结论
/payment 路由收到请求后,没有向客户端返回任何响应。代码执行到:
const { products, email } = req.body;
之后没有调用 res.json()、res.send() 或 res.end(),也没有进入错误处理中间件,所以连接一直处于打开状态。Heroku Router 等待约 30 秒后仍未收到响应,便终止请求、记录 H12 Request timeout,并返回 503。
如果实际路由还调用了 Stripe、数据库或其他外部 API,这些操作长时间没有完成,也会触发 H12。不过就目前给出的代码来看,最直接的问题是路由没有返回响应。
正确处理请求
先在路由之前启用 JSON 请求体解析,并确保请求无论成功还是失败都会结束:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
if (process.env.NODE_ENV === 'production') {
app.use(express.static('clients/build'));
}
app.post('/payment', async (req, res) => {
try {
const { products, email } = req.body;
if (!Array.isArray(products) || !email) {
return res.status(400).json({
error: 'products and email are required'
});
}
// 在这里执行支付会话创建、数据库写入等操作
// const session = await createCheckoutSession(products, email);
return res.status(200).json({
success: true
// sessionId: session.id
});
} catch (error) {
console.error('POST /payment failed:', error);
return res.status(500).json({
error: 'Unable to create payment session'
});
}
});
app.listen(process.env.PORT || 4000, () => {
console.log('Server Start');
});
express.json() 必须放在 /payment 路由之前。否则,客户端发送 Content-Type: application/json 时,req.body 可能是 undefined。
此时直接解构 req.body:
const { products, email } = req.body;
还可能抛出异常。如果项目没有统一的 Express 错误处理,客户端收到的结果会更加混乱。
前端也要处理服务端响应
fetch() 只会在网络请求本身失败时自动抛出异常。即使服务端返回 400、500 或 503,仍然需要主动检查 response.ok:
const response = await fetch(
'https://e-commerce-shopping-1.herokuapp.com/payment',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
},
body: JSON.stringify({
products,
email: email !== null ? email : displayName
})
}
);
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || `Request failed: ${response.status}`);
}
console.log(data);
如果路由还调用了支付接口
支付路由通常还会调用 Stripe 等外部服务,例如:
app.post('/payment', async (req, res) => {
try {
const { products, email } = req.body;
const session = await stripe.checkout.sessions.create({
// 根据实际业务填写参数
});
return res.status(200).json({
sessionId: session.id
});
} catch (error) {
console.error(error);
return res.status(500).json({
error: error.message
});
}
});
排查时应注意以下几点:
await等待的外部请求能否正常完成;- Heroku 中是否已经配置 Stripe Key 等环境变量;
- 是否有 Promise 一直没有 resolve 或 reject;
- 每条条件分支是否都调用了
res.json()、res.send()、res.end()或next(error); - 发送响应后是否遗漏
return,导致代码继续运行并尝试重复响应。
延长客户端等待时间不能解决 H12。Heroku 日志已经表明请求成功连接到了 dyno:
connect=0ms service=30005ms status=503
问题不在于服务器无法连接,而在于应用没有在约 30 秒内完成响应。
CORS 与静态文件不是主要原因
日志中已经出现:
method=POST path="/payment"
这说明 POST 请求确实到达了 Heroku Router。因此,当前 H12 的主要原因不是静态文件配置,也不太可能只是浏览器的 CORS 拦截。
如果前端和后端部署在不同域名,仍然应在后端统一配置 CORS:
app.use(cors({
origin: 'https://your-frontend-domain.example',
methods: ['GET', 'POST', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Accept']
}));
如果 React 页面和 API 由同一个 Heroku 应用提供,可以直接使用相对地址:
fetch('/payment', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
},
body: JSON.stringify({ products, email })
});
先补上 express.json(),再确认 /payment 的成功、参数错误和异常路径都会明确返回响应。如果修改后仍然出现 H12,再检查路由中尚未展示的支付接口、数据库查询或其他异步操作。
备注:内容仅供参考。
