创建与 FormData 一起使用的可读流

Posted

技术标签:

【中文标题】创建与 FormData 一起使用的可读流【英文标题】:Creating a Readable Stream to use with FormData 【发布时间】:2018-12-26 23:30:56 【问题描述】:

我正在使用API to upload a CSV file。我从一个字符串在内存中创建 CSV 文件并使用request 模块上传它。但是,我无法从字符串创建可读流。我在How to create streams from string in Node.Js 上关注了一个 SO 答案。这是我的解决方案代码:

var importResponse = function(csv, callback)
    stringify(csv, function(err, output)

        const s = new Readable();
        s._read = () => ; 
        s.push(output);
        s.push(null);

        request.post(
          headers: 'X-API-TOKEN':token, 'content-type' : 'multipart/form-data',
          url: 'https://ca1.qualtrics.com/API/v3/responseimports',
          formData: 
            surveyId: 'SV_123',
            file: 
                value: s,
                options: 
                    contentType: 'text/csv; charset=utf-8'
                
            
          
        , function(err, res, body)
            if(err || res.statusCode !== 200)
              console.log(err || "Error status code: " + res.statusCode);
              console.log(body);
              return;
            
        );
    );


csv 变量看起来像 [["QID1","QID2"],["1","2"]],而 stringify 的输出看起来像 "QID1,QID2\n,1,2\n"

这个解决方案给了我错误Unexpected end of input

"meta":"httpStatus":"400 - Bad Request","error":"errorMessage":"Unexpected end of input"

如果我改用memfs,它可以正常工作

const fs = require('memfs');

var importResponse = function(csv, callback)
    stringify(csv, function(err, output)
        // Create file in memory
        fs.writeFileSync('/data.csv', output); 

        request.post(
          headers: 'X-API-TOKEN':token, 'content-type' : 'multipart/form-data',
          url: 'https://ca1.qualtrics.com/API/v3/responseimports',
          formData: 
            surveyId: 'SV_123',
            file: 
                value: fs.createReadStream('/data.csv'),
                options: 
                    contentType: 'text/csv; charset=utf-8'
                
            
          
        , function(err, res, body)
            if(err || res.statusCode !== 200)
              console.log(err || "Error status code: " + res.statusCode);
              console.log(body);
              return;
            
        );
    );


如何将stringify 的输出转换为可用于通过 api 上传的 Stream?

【问题讨论】:

我们肯定知道,stringify 来自哪里?您有需要的特定软件包吗? 看起来csv-stringify 可以自己创建流,仅供参考。您可以直接将stringify(csv) 作为“文件”流传递。 @Jacob 是的,这解决了它。将我的代码更改为var output = stringify(csv);,现在可以使用了。还将字符串化模块更改为同步模块。谢谢! 【参考方案1】:

您似乎正在使用request 库。您可能会遇到此警告,如他们的readme 中所述:

// Pass optional meta-data with an 'options' object with style: value: DATA, options: OPTIONS
// Use case: for some types of streams, you'll need to provide "file"-related information manually.
// See the `form-data` README for more information about options: https://github.com/form-data/form-data
custom_file: 
  value:  fs.createReadStream('/dev/urandom'),
  options: 
    filename: 'topsecret.jpg',
    contentType: 'image/jpeg'
  

由于您使用的是非文件流,因此只需提供一个虚拟文件名即可:

request.post(
  headers: 'X-API-TOKEN':token, 'content-type' : 'multipart/form-data',
  url: 'https://ca1.qualtrics.com/API/v3/responseimports',
  formData: 
    surveyId: 'SV_123',
    file: 
      value: s,
      options: 
        contentType: 'text/csv; charset=utf-8',
        filename: 'dummy.csv'
      
    
  
, function(err, res, body)
  if(err || res.statusCode !== 200)
    console.log(err || "Error status code: " + res.statusCode);
    console.log(body);
    return;
  
);

【讨论】:

嗨,Jacob,我尝试添加文件名(以前有),但它仍然不起作用。我想知道问题是否出在Stringify 上。我想也许他们是字符串末尾的换行符导致了它,但事实并非如此。我不确定Stringifymemfs 是否存在问题,在保存时“修复”了问题,或者流本身是否错误,不一定是内容......如果这有意义的话。 是的,就是这个包。抱歉应该更清楚。【参考方案2】:

对于当前节点版本,示例 sn-p 不正确或可能已过时。实现可读性的一种非常简单的方法:

const s = new Readable(
  encoding: 'utf8',
  read(size) 
    // Possibly respect the requested size to make for a good consumer experience
    // Otherwise:
    this.push(output, 'utf8');
    this.push(null); // This signals that there's no more data.
  
);

尊重读者意愿的方法如下:

let data = output;
const s = new Readable(
  encoding: 'utf8',
  read(size) 
    let wantsMore = true;
    while (wantsMore) 
      const chunk = data.slice(0, size);          
      if (!chunk) 
        return void this.push(null);            
      

      wantsMore = this.push(chunk, 'utf8');
      data = data.slice(size);
    
  
);

【讨论】:

感谢您的回复。我尝试添加您的 sn-p 代替前一个,仍然收到相同的 Unexpected end of input 错误。 糟糕,我在发送 EOF 信号时犯了一个错误。试试我的更新。 添加了一些关于编码的注释。这很奇怪。 我想我是在注意到您使用 request 之后才知道的。 “文件”上传很奇怪。查看其他答案。 (暂时搁置,以防流也有问题,但这可能是另一个答案)。

以上是关于创建与 FormData 一起使用的可读流的主要内容,如果未能解决你的问题,请参考以下文章

Nodejs可读流,解析二进制数据,保留顺序

为啥我的数据被解释为可读流而不是 json 对象?

node中间层实现文件上传

从 JavaScript 对象创建 Node.js 可读流——最简单的方法

如何在node.js中将数组值作为可读流发出/管道?

在NodeJS中将可读流关闭到FIFO