3

我的快递服务器从浏览器接收文件上传。上传作为multipart/form-data请求传输;我使用多方来解析传入的实体主体。

Multiparty 允许您将一部分(大致是一个像 an 的表单字段<input type="file">)作为可读流。我不想在我的网络服务器上处理或存储上传的文件,所以我只是将上传的文件部分通过管道发送到另一个服务的请求中(使用请求模块)。

app.post('/upload', function(req, res) {
    var form = new multiparty.Form();

    form.on('part', function(part) {

        var serviceRequest = request({
            method: 'POST',
            url: 'http://other-service/process-file',
            headers: {
                'Content-Type': 'application/octet-stream'
            }
        }, function(err, svcres, body) {
            // handle response
        });

        part.pipe(serviceRequest);
    });

    form.parse(req);
});

这在大多数情况下都能正常工作。节点自动应用分块传输编码,并且随着浏览器上传文件字节,它们被正确地作为原始实体主体(没有多部分格式)发送到后端服务,最终得到完整的文件并成功返回。

但是,有时请求会失败,并且我的回调会被调用err

TypeError: The header content contains invalid characters 
    at ClientRequest.OutgoingMessage.setHeader (_http_outgoing.js:360:11) 
    at new ClientRequest (_http_client.js:85:14) 
    at Object.exports.request (http.js:31:10) 
    at Object.exports.request (https.js:199:15) 
    at Request.start (/app/node_modules/request/request.js:744:32) 
    at Request.write (/app/node_modules/request/request.js:1421:10) 
    at PassThrough.ondata (_stream_readable.js:555:20) 
    at emitOne (events.js:96:13) 
    at PassThrough.emit (events.js:188:7) 
    at PassThrough.Readable.read (_stream_readable.js:381:10) 
    at flow (_stream_readable.js:761:34) 
    at resume_ (_stream_readable.js:743:3) 
    at _combinedTickCallback (internal/process/next_tick.js:80:11) 
    at process._tickDomainCallback (internal/process/next_tick.js:128:9) 

我无法解释该错误来自何处,因为我只设置了Content-Type标题并且堆栈不包含我的任何代码。

为什么我的上传偶尔会失败?

4

3 回答 3

11

此示例显示如何将文件作为附件发送,文件名中包含国家符号。

const http = require('http');
const fs = require('fs');
const contentDisposition = require('content-disposition');
...

// req, res - http request and response
let filename='totally legit .pdf';
let filepath = 'D:/temp/' + filename;               

res.writeHead(200, {
    'Content-Disposition': contentDisposition(filename), // Mask non-ANSI chars
    'Content-Transfer-Encoding': 'binary',
    'Content-Type': 'application/octet-stream'
});

var readStream = fs.createReadStream(filepath);
readStream.pipe(res);
readStream.on('error', (err) => ...);
于 2019-05-31T09:57:46.393 回答
6

如果请求选项对象中的任何字符串包含基本 ASCII 范围之外的字符,则节点在发出传出 HTTP 请求时TypeError 会抛出该错误。headers

在这种情况下,似乎Content-Disposition在请求中设置了标头,即使它从未在请求​​选项中指定。由于该标头包含上传的文件名,如果文件名包含非 ASCII 字符,则可能导致请求失败。IE:

POST /upload HTTP/1.1
Host: public-server
Content-Type: multipart/form-data; boundary=--ex
Content-Length: [bytes]

----ex
Content-Disposition: form-data; name="file"; filename="totally legit .pdf"
Content-Type: application/pdf

[body bytes...]
----ex--

然后请求other-service/process-file失败,因为多方将部件标头存储在part对象上,该对象也是表示部件主体的可读流。当您pipe()进入partserviceRequest,请求模块会查看管道流是否具有headers属性,如果有,则将它们复制到传出请求标头

这会产生如下所示的传出请求:

POST /process-file HTTP/1.1
Host: other-service
Content-Type: application/octet-stream
Content-Disposition: form-data; name="file"; filename="totally legit .pdf"
Content-Length: [bytes]

[body bytes...]

...除了该节点在标头中看到非 ASCII 字符Content-Disposition并抛出。抛出的错误被请求捕获并作为err.

这种行为可以通过在将其传递到请求之前删除部分标头来避免。

delete part.headers;
part.pipe(serviceRequest);
于 2017-09-07T21:31:25.080 回答
0

就像之前的 @arrow cmt 一样,在 Content-disposition 标头上使用 encodeURI(filename) 。在客户端,您使用 decodeURI 方法进行解码。

于 2022-02-22T07:38:32.450 回答