【发布时间】:2016-08-26 20:06:47
【问题描述】:
情况:
从 Node.js(通过节点核心 HTTPS 模块)向 spring-boot Java API 提交多部分表单请求。 API 需要两个表单数据元素:
“路线”
“文件”
完全错误:
Exception processed - Main Exception:
org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is org.apache.commons.fileupload.FileUploadException: Stream ended unexpectedly
请求标头:
{"Accept":"*/*",
"cache-control":"no-cache",
"Content-Type":"multipart/form-data; boundary=2baac014-7974-49dd-ae87-7ce56c36c9e7",
"Content-Length":7621}
FORM-DATA BEING WRITTEN(全部以二进制形式写入):
Content-Type: multipart/form-data; boundary=2baac014-7974-49dd-ae87-7ce56c36c9e7
--2baac014-7974-49dd-ae87-7ce56c36c9e7
Content-Disposition:form-data; name="route"
...our route object
--2baac014-7974-49dd-ae87-7ce56c36c9e7
Content-Disposition:form-data; name="files"; filename="somefile.xlsx"
Content-Type:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
...excel file contents
--2baac014-7974-49dd-ae87-7ce56c36c9e7--
节点代码:
let mdtHttpMultipart = (options, data = reqParam('data'), cb) => {
const boundaryUuid = getUuid()
, baseHeaders = {
'Accept': '*/*',
'cache-control': 'no-cache'
}
, composedHeaders = Object.assign({}, baseHeaders, options.headers)
;
options.path = checkPath(options.path);
let composedOptions = Object.assign({}, {
'host': getEdiHost(),
'path': buildPathFromObject(options.path, options.urlParams),
'method': options.method || 'GET',
'headers': composedHeaders,
'rejectUnauthorized': false
});
composedOptions.headers['Content-Type'] = `multipart/form-data; boundary=${boundaryUuid}`;
let multipartChunks = [];
let dumbTotal = 0;
let writePart = (_, encType = 'binary', skip = false) => {
if (!_) { return; }
let buf = Buffer.from(_, encType);
if (!skip) {dumbTotal += Buffer.byteLength(buf, encType);}
multipartChunks.push(buf);
};
writePart(`Content-Type: multipart/form-data; boundary=${boundaryUuid}\r\n\r\n`, 'binary', true)
writePart(`--${boundaryUuid}\r\n`)
writePart(`Content-Disposition:form-data; name="route"\r\n`)
writePart(JSON.stringify(data[0]) + '\r\n')
writePart(`--${boundaryUuid}\r\n`)
writePart(`Content-Disposition:form-data; name="files"; filename="${data[1].name}"\r\n`)
writePart(`Content-Type:${data[1].contentType}\r\n`)
writePart(data[1].contents + '\r\n')
writePart(`\r\n--${boundaryUuid}--\r\n`);
let multipartBuffer = Buffer.concat(multipartChunks);
composedOptions.headers['Content-Length'] = dumbTotal;
let request = https.request(composedOptions);
// on nextTick write multipart to request
process.nextTick(() => {
request.write(multipartBuffer, 'binary');
request.end();
});
// handle response
request.on('response', (httpRequestResponse) => {
let chunks = []
, errObject = handleHttpStatusCodes(httpRequestResponse);
;
if (errObject !== null) {
return cb(errObject, null);
}
httpRequestResponse.on('data', (chunk) => { chunks.push(chunk); });
httpRequestResponse.on('end', () => {
let responseString = Buffer.concat(chunks).toString()
;
return cb(null, JSON.parse(responseString));
});
});
request.on('error', (err) => cb(err));
};
根据规范,我们看不出有任何理由抛出 500。在这里对格式进行了大量的修改,但我们还没有正确地实现结果。
旁注:它适用于我们使用 POSTMAN,但无法使用我们自己的应用程序服务器(我们实际构建 excel 文件的地方)。
任何帮助都将不胜感激,即使只是尝试的想法。
【问题讨论】:
-
可能不是问题,但是当您第一次调用
writePart()时,您添加的是在指定composedOptions.headers['Content-Type'] = ...时已经为您添加的 Content-Type 标头。您也不需要在最后的--${boundaryUuid}--周围换行。 -
继续进行这些更改,结果仍与预期相同。
-
您确定
binary编码?这是latin1的别名。看看base64对所有内容进行编码是否有什么不同? -
所以需要二进制的原因是由于 excel 文件的内容。我们实际上是从一个单独的微服务接收该文件,该微服务将其构建在内存中并将其发布到一个单独的 API,在那里它以二进制形式传输它。使用该文件(作为二进制文件)执行单个多部分表单请求对我们有用,但添加额外的 JSON 表单数据对象(在本例中为“路由”)会使同一服务器返回 500 个。
标签: node.js spring http multipartform-data