简体   繁体   English

将分段上传部分通过管道传输到新请求时出现“标头内容包含无效字符”错误

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

My express server receives file uploads from browsers.我的快递服务器接收来自浏览器的文件上传。 The uploads are transferred as multipart/form-data requests;上传作为multipart/form-data请求传输; I use multiparty to parse the incoming entity body.我使用多方来解析传入的实体主体。

Multiparty allows you to get a part (roughly, a single form field like an <input type="file"> ) as a readable stream. I do not want to process or store the uploaded file(s) on my web server, so I just pipe the uploaded file part into a request made to another service (using the request module). Multiparty 允许您将一部分(大致上,单个表单字段,如<input type="file"> )作为可读的 stream。我不想在我的 web 服务器上处理或存储上传的文件,所以我只是将 pipe 上传的文件部分转换为对另一个服务的请求(使用请求模块)。

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);
});

This works correctly most of the time.这在大多数情况下都能正常工作。 node automatically applies chunked transfer encoding, and as the browser uploads file bytes, they are correctly sent to the backend service as a raw entity body (without the multipart formatting), which ultimately gets the complete file and returns successfully. node自动应用分块传输编码,随着浏览器上传文件字节,它们作为原始实体主体(没有多部分格式化)正确发送到后端服务,最终得到完整的文件并成功返回。

However, sometimes the request fails and my callback gets called with this err :但是,有时请求失败并且我的回调被调用并出现此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) 

I'm unable to explain where that error is coming from since I only set the Content-Type header and the stack does not contain any of my code.我无法解释该错误的来源,因为我只设置了Content-Type header 并且堆栈不包含我的任何代码。

Why do my uploads occasionally fail?为什么我的上传偶尔会失败?

That TypeError gets 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 . 如果请求headers选项对象中有任何字符串包含基本ASCII范围之外的字符,则发出传出HTTP请求时,该TypeError 将由节点抛出

In this case, it appears that the Content-Disposition header is getting set on the request even though it is never specified in the request options. 在这种情况下,即使请求选项中从未指定Content-Disposition标头,也似乎已在请求中设置了标头。 Since that header contains the uploaded filename, this can result in the request failing if the filename contains non-ASCII characters. 由于该标头包含上载的文件名,因此如果文件名包含非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--

The request to other-service/process-file then fails because multiparty stores the part headers on the part object, which is also a readable stream representing the part body. 然后,对other-service/process-file的请求失败,因为多方将零件标头存储在part对象上,这也是表示零件主体的可读流。 When you pipe() the part into serviceRequest , the request module looks to see if the piped stream has a headers property, and if it does, copies them to the outgoing request headers . part pipe() 传递serviceRequest ,请求模块将查看管道流是否具有headers属性,如果有,则将其复制到传出请求headers中

This results in the outgoing request that would look like: 这导致传出请求看起来像:

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...]

...except that node sees the non-ASCII character in the Content-Disposition header and throws. ...除了该节点在Content-Disposition标头中看到非ASCII字符并抛出外。 The thrown error is caught by request and passed to the request callback function as err . 引发的错误被请求捕获,并以err传递给请求回调函数。

This behavior can be avoided by removing the part headers before piping it into the request. 可以通过在将管道输送到请求中之前除去零件头来避免此行为。

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

This example shows how to send file as an attachment with national symbols in the filename. 本示例说明如何将文件作为附件发送,文件名中带有国家符号。

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

As like as @arrow cmt before, using encodeURI(filename) on your Content-disposition header. In client, you using decodeURI method to decode.就像之前的@arrow cmt 一样,在您的 Content-disposition header 上使用 encodeURI(filename)。在客户端中,您使用 decodeURI 方法进行解码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM