org.springframework.web.multipart.MultipartException:无法parsing多部分servlet请求…stream意外结束

情况:

从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 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正在写入(全部写成二进制):

 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的理由。 在这里修改格式的吨,但我们还没有正确实现的结果。

SIDE NOTE:它适用于我们使用POSTMAN,只是无法使用我们自己的应用程序服务器(我们实际上build立的Excel文件)的工作。

任何帮助,将不胜感激,即使只是想法尝试。

尝试这个:

 let mdtHttpMultipart = (options, data = reqParam('data'), cb) => { const boundaryUuid = getUuid() , baseHeaders = { 'Accept': '*/*', 'cache-control': 'no-cache' } , composedHeaders = Object.assign({}, baseHeaders, options.headers) ; let file = data[1] let xlsx = file.contents 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 }); let header = Buffer.from(`--${boundaryUuid} Content-Disposition: form-data; name="route" ${JSON.stringify(data[0])}) --${boundaryUuid} Content-Disposition: form-data; name="files"; filename="${file.name}" Content-Type: ${file.contentType} `.replace(/\r?\n */gm, '\r\n')) let footer = Buffer.from(`\r\n--${boundaryUuid}--`) let length = header.length + xlsx.length + footer.length let body = Buffer.concat([header, xlsx, footer], length) composedOptions.headers['Content-Length'] = length; composedOptions.headers['Content-Type'] = `multipart/form-data; boundary=${boundaryUuid}`; let request = https.request(composedOptions); // 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)); // write multipart to request request.end(body); }; 

是不是在任何地方调用request.end()

用一个正文发送请求的(非常一般的)forms是https.request(opts).end(body)

另外,你可以在每次你想发送数据时调用request.write(buf)而不是累积到一个巨大的缓冲区中(你不需要在nextTicknextTick )(编辑:如OP在注释中指出的那样,这将防止设置Content-Length ,所以也许保持不变)