Firebase Functions(Node.js)发起HTTPS POST提交Office表单报400错误
问题描述
我正在创建Firebase Cloud Function实现自动提交Microsoft Office在线表单的功能,编写的每小时定时触发函数代码如下:
exports.covidForm = functions.pubsub.schedule('every 1 hours').onRun((context) => { var data = `{ "startDate": "2022-07-04T05:27:42.157Z", "submitDate": "2022-07-04T05:31:30.748Z", "answers": "[{\"questionId\":\"rc4588b6d25e8418b9dc6ead534c83ae9\",\"answer1\":\"Name\"},{\"questionId\":\"r3e87261178f94361b4fa8b61bb6d62e4\",\"answer1\":\"ID\"},{\"questionId\":\"r357b74d9ed964c3da24f0586f8cb7332\",\"answer1\":\"email\"},{\"questionId\":\"r14db4cece08b49a09f249e7e87d06c51\",\"answer1\":\"site\"},{\"questionId\":\"rc85571bd0c0b4027833c7d2f18f38b53\",\"answer1\":\"No\"},{\"questionId\":\"ra9a3c908d3204e21b37c08bec50f983d\",\"answer1\":\"No\"},{\"questionId\":\"rcf571f318ea54bffa41364a4f967d88c\",\"answer1\":\"No\"},{\"questionId\":\"ra09058c8546e489e865cd6ffd862c23c\",\"answer1\":\"No\"}]" }`; fetch("https://forms.office.com/formapi/api/.../forms('...')/responses", { method: 'POST', body: data, headers: { 'Content-Type': 'application/json' } }) .then(res => res.json()) .then(json => functions.logger.log(json)) .catch (err => functions.logger.log(err)) });
运行函数时收到如下400错误:
{"errors":{"":["The input was not valid."]},"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"00-e52550913c0d8961deb46df79763ae95-06127511b66b74a6-00"}
我在reqbin.com平台使用完全相同的JSON数据发起HTTPS POST请求可以成功提交表单,无法定位Firebase Functions环境下请求执行失败的原因,reqbin成功发起请求的截图如下:
故障原因
代码存在4个核心问题直接导致请求失败:
- 没有返回
fetch调用的Promise链:Firebase Cloud Function执行时如果检测到回调里没有待处理的异步任务,会直接回收函数实例终止运行,当前写法下fetch发起后没有返回,函数很可能在请求还没完整发送到Office服务器的时候就被关闭,服务端收到残缺的请求体自然返回「输入无效」的400错误。 - 手动拼接JSON字符串容错率极低:用模板字符串硬写多层嵌套的JSON结构,转义符很容易出现多写漏写的问题,哪怕一个转义符错误都会让请求体不符合接口格式要求。
- 运行时兼容问题:Node.js 16及更早版本的Firebase Functions运行时没有内置全局
fetch方法,如果没手动引入fetch依赖,调用行为本身就不符合预期。 - 缺少必要请求头:Office Forms接口会校验请求来源特征,默认的服务端请求头很容易被拦截判定为非法请求。
修复方案
按以下步骤修改代码即可正常运行:
- 如果使用Node.js 16及更低版本的运行时,先在函数项目目录执行
npm install node-fetch@2安装fetch依赖(v2版本支持CommonJS规范,适配Firebase Functions默认运行环境,v3为ESM模块不推荐直接使用),在代码顶部引入fetch。 - 不要手动拼接JSON字符串,直接构造原生JavaScript对象,发送请求时用
JSON.stringify()自动完成序列化,彻底避免转义错误。 - 把fetch的整个Promise链return给onRun回调,保证函数会等待请求执行完成后再退出。
- 补充常用的浏览器请求头,绕过接口的基础反爬校验。
- 不要硬编码固定的
startDate和submitDate,每次提交时动态生成当前时间,避免被接口判定为重复无效提交。
修复后的完整代码如下:
// Node16及以下版本加这行引入fetch,Node18+内置全局fetch可省略 const fetch = require('node-fetch'); exports.covidForm = functions.pubsub.schedule('every 1 hours').onRun((context) => { // 直接构造JS对象,不要手动拼JSON字符串 const requestData = { startDate: new Date().toISOString(), submitDate: new Date().toISOString(), answers: JSON.stringify([ {"questionId":"rc4588b6d25e8418b9dc6ead534c83ae9","answer1":"Name"}, {"questionId":"r3e87261178f94361b4fa8b61bb6d62e4","answer1":"ID"}, {"questionId":"r357b74d9ed964c3da24f0586f8cb7332","answer1":"email"}, {"questionId":"r14db4cece08b49a09f249e7e87d06c51","answer1":"site"}, {"questionId":"rc85571bd0c0b4027833c7d2f18f38b53","answer1":"No"}, {"questionId":"ra9a3c908d3204e21b37c08bec50f983d","answer1":"No"}, {"questionId":"rcf571f318ea54bffa41364a4f967d88c","answer1":"No"}, {"questionId":"ra09058c8546e489e865cd6ffd862c23c","answer1":"No"} ]) }; // 一定要return整个Promise链 return fetch("https://forms.office.com/formapi/api/.../forms('...')/responses", { method: 'POST', body: JSON.stringify(requestData), headers: { 'Content-Type': 'application/json', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Origin': 'https://forms.office.com', 'Referer': 'https://forms.office.com/' } }) .then(res => { if (!res.ok) throw new Error(`请求失败,状态码:${res.status}`); return res.json(); }) .then(json => functions.logger.log("表单提交成功:", json)) .catch (err => functions.logger.error("表单提交出错:", err)) });
内容的提问来源于stack exchange,提问作者Joshua Jack
相关产品推荐
相关产品推荐

