"Header content contains invalid characters" 将分段上传部分传送到新请求时出错

"Header content contains invalid characters" error when piping multipart upload part into a new request

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

Multiparty 允许您将 part(大致上,像 <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);
});

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

但是,有时 请求失败并且我的回调被调用 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 header 并且堆栈不包含我的任何代码。

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

TypeErrorgets thrown by node when making an outgoing HTTP request if there is any string in the request headers option object contains a character outside the basic ASCII range

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

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的请求失败了,因为multiparty将header部分存储在partobject上,这也是代表[=]部分的可读流40=]。当你pipe()part改成serviceRequest时,请求模块looks to see if the piped stream has a headers property, and if it does, copies them to the outgoing request 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...]

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

传递给请求回调函数

可以通过在将 headers 部分通过管道传输到请求中之前将其删除来避免这种行为。

delete part.headers;
part.pipe(serviceRequest);

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

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) => ...);

与之前的@arrow cmt 一样,在您的Content-disposition header 上使用encodeURI(filename)。在客户端,你使用decodeURI方法解码。