使用http.request在node.js中获取二进制内容

71

我想从https请求中检索二进制数据。

我发现一个类似的问题,使用请求方法Getting binary content in Node.js using request,它说将编码(encoding)设置为null就可以了,但实际上并不行。

options = {
    hostname: urloptions.hostname,
    path: urloptions.path,
    method: 'GET',
    rejectUnauthorized: false,
    encoding: null
};

req = https.request(options, function(res) {
    var data;
    data = "";
    res.on('data', function(chunk) {
        return data += chunk;
    });
    res.on('end', function() {
        return loadFile(data);
    });
    res.on('error', function(err) {
        console.log("Error during HTTP request");
        console.log(err.message);
    });
})

编辑:将编码设置为'binary'也不起作用。


如果您知道要应用于数据的编码,那么将其转换为二进制不是非常容易吗?我的意思是,这是一台计算机,您别无选择,只能接收二进制数据... - MobA11y
7个回答

108

对我来说,被接受的答案并没有起作用(即将编码设置为二进制),即使提问者提到它也没有起作用。

以下是对我有用的内容,取自:http://chad.pantherdev.com/node-js-binary-http-streams/

http.get(url.parse('http://myserver.com:9999/package'), function(res) {
    var data = [];

    res.on('data', function(chunk) {
        data.push(chunk);
    }).on('end', function() {
        //at this point data is an array of Buffers
        //so Buffer.concat() can make us a new Buffer
        //of all of them together
        var buffer = Buffer.concat(data);
        console.log(buffer.toString('base64'));
    });
});

编辑:根据Semicolon的建议更新答案。


我遇到了这个问题;对我来说,使用get()选项将编码设置为null确实有效。作为参考,尝试通过请求模块的默认值设置编码是不起作用的。 - TheDiveO
直到我将“end”和“finish”交换位置,这才对我起作用。 - cpres
1
如果您的Node版本中没有Buffer.concat()函数,则Buffer.from()函数也可以接受一个数组。 - Spechal

26

在AWS Lambda环境中使用NodeJS 6.10(以及8.10,在2019年2月进行了测试),上述解决方案都对我无效。

对我有效的是以下内容:

https.get(opt, (res) => {
    res.setEncoding('binary');
    let chunks = [];

    res.on('data', (chunk) => {
        chunks.push(Buffer.from(chunk, 'binary'));
    });

    res.on('end', () => {
        let binary = Buffer.concat(chunks);
        // binary is now a Buffer that can be used as Uint8Array or as
        // any other TypedArray for data processing in NodeJS or 
        // passed on via the Buffer to something else.
    });
});

请注意 res.setEncoding('binary'); 和 Buffer.from(chunk, 'binary') 这两行代码。其中一行设置响应编码,另一行从之前指定的编码中创建一个 Buffer 对象。


20

你需要将编码设置为响应,而不是请求:

req = https.request(options, function(res) {
    res.setEncoding('binary');

    var data = [ ];

    res.on('data', function(chunk) {
        data.push(chunk);
    });
    res.on('end', function() {
        var binary = Buffer.concat(data);
        // binary is your data
    });
    res.on('error', function(err) {
        console.log("Error during HTTP request");
        console.log(err.message);
    });
});

这里有一个有用的答案:将图片写入本地服务器


3
这段代码表示数据需要是一个缓冲区数组,但实际传入的是字符串。 - makc

8
  1. 不要调用 setEncoding() 方法,因为默认情况下,没有分配编码,流数据将以 Buffer 对象的形式返回
  2. on.data 回调方法中调用 Buffer.from(),将 chunk 值转换为 Buffer 对象。
http.get('my_url', (response) => {
  const chunks = [];
  response.on('data', chunk => chunks.push(Buffer.from(chunk))) // Converte `chunk` to a `Buffer` object.
    .on('end', () => {
      const buffer = Buffer.concat(chunks);
      console.log(buffer.toString('base64'));
    });
});

7
我希望能够评论,感谢你从一整天中的递归循环中拯救了我,让我不再抓狂,还有感谢你提供的(非常有用的)答案,我已经去查找文档,但是我甚至无法找到res.setEncoding方法的文档!它只被作为两个例子的一部分显示,在这些例子中,他们调用了 res.setEncoding('utf8');。你是在哪里找到这个信息的?或者你是如何发现的?
由于我的声望不足以评论,所以我会用我的答案做出贡献:Pärt Johanson的答案完全适用于我,我只是稍微修改了一下,因为我正在使用它来下载和评估在我的服务器上托管的脚本(并使用nwjc编译),使用 nw.Window.get().evalNWBin() 在 NWJS 0.36.4 / Node 11.11.0 上。
let opt = {...};
let req = require('https').request(opt, (res) => {
  // server error returned
  if (200 !== res.statusCode) {
    res.setEncoding('utf8');
    let data = '';
    res.on('data', (strData) => {
      data += strData;
    });
    res.on('end', () => {
      if (!res.complete) {
        console.log('Server error, incomplete response: ' + data);
      } else {
        console.log('Server error, response: ' + data);
      }
    });
  }
  // expected response
  else {
    res.setEncoding('binary');
    let data = [];
    res.on('data', (binData) => {
      data.push(Buffer.from(binData, 'binary'));
    });
    res.on('end', () => {
      data = Buffer.concat(data);
      if (!res.complete) {
        console.log('Request completed, incomplete response, ' + data.length + ' bytes received');
      } else {
        console.log('Request completed, ' + data.length + ' bytes received');
        nw.Window.get().evalNWBin(null, data);
      }
    });
  }
};

编辑:附言,我发表这篇文章是为了让任何人都知道如何处理非二进制响应——我的实际代码会更深入地检查响应内容类型标头,以解析JSON(意图失败,即400、401、403)或HTML(意外失败,即404或500)。


2

大家的回答都没错,但是为了澄清问题,你不能调用.setEncoding()

如果你调用了.setEncoding(),它将创建一个StringDecoder并将其设置为默认解码器。如果你尝试传递nullundefined,它仍将创建一个带有默认解码器UTF-8StringDecoder 。即使你调用.setEncoding('binary'),这与调用.setEncoding('latin1')相同。是的,真的

我希望我可以让你将._readableState.encoding_readableState.decoder设置回null,但是当你调用.setEncoding()时,缓冲区被清除并替换为先前存在的解码字符串的二进制编码。这意味着你的数据已经被更改了。

如果你想“撤销”解码,你必须将数据流重新编码为二进制,像这样:

  req.on('data', (chunk) => {
      let buffer;
      if (typeof chunk === 'string') {
        buffer = Buffer.from(chunk, req.readableEncoding);
      } else {
        buffer = chunk;
      }
      // Handle chunk
  });

当然,如果你从未调用.setEncoding(),那么你不必担心块被返回为string


当你有一个Buffer块后,你可以根据自己的选择对其进行操作。为了全面起见,以下是如何在预设缓冲区大小的情况下使用它,并同时检查Content-Length

const BUFFER_SIZE = 4096;

/**
 * @param {IncomingMessage} req
 * @return {Promise<Buffer>}
 */
function readEntireRequest(req) {
  return new Promise((resolve, reject) => {
    const expectedSize = parseInt(req.headers['content-length'], 10) || null;
    let data = Buffer.alloc(Math.min(BUFFER_SIZE, expectedSize || BUFFER_SIZE));
    let bytesWritten = 0;
    req.on('data', (chunk) => {
      if ((chunk.length + bytesWritten) > data.length) {
        // Buffer is too small. Double it.
        let newLength = data.length * 2;
        while (newLength < chunk.length + data.length) {
          newLength *= 2;
        }
        const newBuffer = Buffer.alloc(newLength);
        data.copy(newBuffer);
        data = newBuffer;
      }
      bytesWritten += chunk.copy(data, bytesWritten);
      if (bytesWritten === expectedSize) {
        // If we trust Content-Length, we could return immediately here.
      }
    });
    req.on('end', () => {
      if (data.length > bytesWritten) {
        // Return a slice of the original buffer
        data = data.subarray(0, bytesWritten);
      }
      resolve(data);
    });
    req.on('error', (err) => {
      reject(err);
    });
  });
}

选择在这里使用缓冲区大小是为了避免立即保留大量的内存,而只在需要时获取RAM。“Promise”功能只是为方便而添加的。

2
与其他人一样,我需要处理来自Node.js HTTP响应(也称为http.IncomingMessage)的二进制数据块。
除了Pärt Johanson的答案和其变体外,现有的答案都不适用于我的Electron 6项目(在发布时使用Node.js 12.4.0)。
即使使用该解决方案,这些块始终作为字符串对象到达response.on('data', ondata)处理程序(而不是期望和所需的Buffer对象)。这需要额外的转换Buffer.from(chunk,'binary')。无论我是否明确指定了二进制编码response.setEncoding('binary')response.setEncoding(null),我都会得到字符串。
我唯一成功获得原始Buffer块的方法是response管道传输到一个stream.Writable实例中,在那里提供自定义write方法
const https = require('https');
const { Writable } = require('stream');

async function getBinaryDataAsync(url) {
  // start HTTP request, get binary response
  const { request, response } = await new Promise((resolve, reject) => {
    const request = https.request(url, { 
      method: 'GET', 
        headers: { 
          'Accept': 'application/pdf', 
          'Accept-Encoding': 'identity'
        }        
      }
    );

    request.on('response', response => 
      resolve({request, response}));
    request.on('error', reject);
    request.end();
  });

  // read the binary response by piping it to stream.Writable
  const buffers = await new Promise((resolve, reject) => {

    response.on('aborted', reject);
    response.on('error', reject);

    const chunks = [];

    const stream = new Writable({
      write: (chunk, encoding, notifyComplete) => {
        try {
          chunks.push(chunk);
          notifyComplete();      
        }
        catch(error) {
          notifyComplete(error);      
        }
      }
    });

    stream.on('error', reject);
    stream.on('finish', () => resolve(chunks));
    response.pipe(stream);
  });

  const buffer = Buffer.concat(buffers);
  return buffer.buffer; // as ArrayBuffer
}

async function main() {
  const arrayBuff = await getBinaryDataAsync('https://download.microsoft.com/download/8/A/4/8A48E46A-C355-4E5C-8417-E6ACD8A207D4/VisualStudioCode-TipsAndTricks-Vol.1.pdf');
  console.log(arrayBuff.byteLength);
};

main().catch(error => console.error(error));

更新:事实证明,这种行为只在我们的Web API服务器上表现出来。因此,对于我在上面的代码片段中使用的示例URL,response.on('data')实际上很好用,不需要流。尽管这是特定于服务器的奇怪现象,但我正在进一步调查它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接