使用apollo-datasource-rest发起multipart-form POST请求遇400错误如何解决
错误原因排查
apollo-datasource-rest默认会将POST请求的参数序列化为JSON格式,即使手动设置了multipart/form-data请求头,也没有实际将参数转换为对应的表单格式,且手动设置的请求头缺少multipart要求的boundary分隔符参数,服务端无法正常解析请求体,这是返回400错误的核心原因。- Salesforce OAuth2接口返回的访问令牌字段为蛇形格式
access_token,代码中错误使用驼峰格式accessToken解构,即使请求成功也无法正常获取令牌。 - 在构造函数中执行异步的令牌获取逻辑,会存在时序问题:后续业务接口发起请求时,令牌请求可能还未返回,导致请求头未携带有效令牌。
正确实现方案
首先安装form-data依赖,用于构造符合标准的multipart-form格式请求体:
npm install form-data
修改后的完整代码如下:
const { RESTDataSource } = require('apollo-datasource-rest'); const FormData = require('form-data'); process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; class SalesforceApi extends RESTDataSource { constructor() { super(); this.initialize({}); // 缓存令牌避免重复请求 this.salesforceToken = null; } // 内置钩子,每次发起请求前自动执行,处理令牌逻辑 async willSendRequest(request) { if (!this.salesforceToken) { this.salesforceToken = await this.getAccessToken(); } request.headers.set('Authorization', `Bearer ${this.salesforceToken}`); } async getAccessToken() { console.log('Getting Salesforce access token'); try { // 构造form-data请求体 const form = new FormData(); form.append('username', 'FILTERED@FILTERED'); form.append('password', 'FILTERED'); form.append('grant_type', 'password'); form.append('client_id', 'FILTERED'); form.append('client_secret', 'FILTERED'); const response = await this.post( 'https://test.salesforce.com/services/oauth2/token', form, { // 禁用默认的JSON序列化,直接返回form实例作为请求体 serializer: (body) => body, // 自动生成带boundary的正确请求头 headers: form.getHeaders() } ); // 使用正确的字段名解构返回值 const { access_token } = response; console.log(`Salesforce access token: ${access_token}`); return access_token; } catch (error) { console.log(`Get token error: ${error}`); throw error; } } } module.exports = SalesforceApi;
补充说明
如果不需要上传文件,Salesforce的OAuth2密码授权流也支持application/x-www-form-urlencoded格式,实现更简单,只需要将请求配置修改为:
const response = await this.post( 'https://test.salesforce.com/services/oauth2/token', new URLSearchParams({ username: 'FILTERED@FILTERED', password: 'FILTERED', grant_type: 'password', client_id: 'FILTERED', client_secret: 'FILTERED', }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } } );
内容的提问来源于stack exchange,提问作者zabumba
相关产品推荐
相关产品推荐

