使用Satellizer+Node.js实现Facebook登录,Heroku遇H12超时求助
看起来你遇到的H12超时(503服务不可用)是Heroku上常见的请求超时问题,结合你的代码和场景,我整理了几个关键排查点和修复方案:
1. 确认Heroku环境变量配置
本地开发时你可能用了.env文件存储FACEBOOK_SECRET,但Heroku不会自动读取这个文件。你需要在Heroku控制台的Settings > Config Vars里手动添加FACEBOOK_SECRET,确保它和你本地使用的密钥完全一致。如果密钥缺失或不正确,请求Facebook OAuth接口会失败,导致整个请求卡住超时。
2. 修正redirectUri的域名
本地测试时redirectUri可能是http://localhost:xxxx/...,但部署到Heroku后,必须换成你的Heroku应用域名(比如https://your-app-name.herokuapp.com/auth/facebook/callback,具体要和Satellizer前端配置的redirectUri一致)。如果redirectUri不匹配,Facebook会拒绝授权请求,导致后端请求一直等待响应,最终触发H12超时。
3. 替换过时的request库并优化异步流程
request库已经被官方标记为废弃(deprecated),在Node.js新版本或Heroku环境下可能存在兼容性问题。建议换成axios或node-fetch这类维护活跃的HTTP库,同时用async/await替代嵌套回调,让代码更清晰,也更容易捕获错误避免请求挂起。
比如修改后的代码示例:
const axios = require('axios'); app.post('/auth/facebook', async function(req, res) { try { const fields = ['id', 'email', 'first_name', 'last_name', 'link', 'name']; const accessTokenUrl = 'https://graph.facebook.com/v2.5/oauth/access_token'; const graphApiUrl = `https://graph.facebook.com/v2.5/me?fields=${fields.join(',')}`; const params = { code: req.body.code, client_id: req.body.clientId, client_secret: config.FACEBOOK_SECRET, redirect_uri: req.body.redirectUri }; // 步骤1:换取访问令牌,设置10秒超时 const accessTokenResponse = await axios.get(accessTokenUrl, { params, timeout: 10000 }); const accessToken = accessTokenResponse.data; if (accessTokenResponse.status !== 200) { return res.status(500).send({ message: accessToken.error.message }); } // 步骤2:获取用户资料,设置10秒超时 const profileResponse = await axios.get(graphApiUrl, { params: accessToken, timeout: 10000 }); const profile = profileResponse.data; if (profileResponse.status !== 200) { return res.status(500).send({ message: profile.error.message }); } // 步骤3:处理用户关联/创建逻辑 if (req.header('Authorization')) { // 已有用户关联Facebook账户的逻辑 const existingUser = await User.findOne({ facebook: profile.id }); if (existingUser) { return res.status(409).send({ message: 'There is already a Facebook account that belongs to you' }); } const token = req.header('Authorization').split(' ')[1]; const payload = jwt.decode(token, config.TOKEN_SECRET); const user = await User.findById(payload.sub); if (!user) { return res.status(400).send({ message: 'User not found' }); } user.facebook = profile.id; user.picture = user.picture || `https://graph.facebook.com/v2.3/${profile.id}/picture?type=large`; user.displayName = user.displayName || profile.name; await user.save(); const jwtToken = createJWT(user); return res.send({ token: jwtToken }); } else { // 新用户创建或返回已有账户 const existingUser = await User.findOne({ facebook: profile.id }); if (existingUser) { const jwtToken = createJWT(existingUser); return res.send({ token: jwtToken }); } const user = new User({ facebook: profile.id, picture: `https://graph.facebook.com/${profile.id}/picture?type=large`, displayName: profile.name }); await user.save(); const jwtToken = createJWT(user); res.send({ token: jwtToken }); } } catch (err) { console.error('Facebook Auth Error:', err); return res.status(500).send({ message: 'Authentication failed due to server error' }); } });
4. 添加请求超时限制
Heroku的H12超时阈值是30秒,所以给所有第三方请求(比如调用Facebook API)设置合理的超时时间(比如10秒),避免请求无限等待。上面的示例中已经给axios添加了timeout: 10000配置。
5. 排查数据库连接问题
如果你的数据库是本地的(比如本地MongoDB),部署到Heroku后必须切换到远程数据库服务(比如MongoDB Atlas)。如果后端无法连接数据库,整个请求会卡住,最终触发H12超时。确保Heroku的Config Vars里配置了正确的数据库连接字符串。
6. 查看Heroku实时日志定位问题
用命令heroku logs --tail查看实时日志,你能看到请求具体卡在哪个环节:是获取accessToken超时?还是数据库查询超时?日志会给出更明确的错误信息,帮助你精准定位。
内容的提问来源于stack exchange,提问作者Rajat Sharma

