59

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

我发现一个使用请求方法的类似问题Getting binary content in Node.js using request,说将编码设置为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);
    });
})

编辑:将编码设置为“二进制”也不起作用

4

7 回答 7

101

接受的答案对我不起作用(即,将编码设置为二进制),即使是提出问题的用户也提到它不起作用。

这对我有用,取自: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'));
    });
});

编辑:根据分号的建议更新答案

于 2014-01-09T15:46:14.257 回答
19

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

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

这是有用的答案:将图像写入本地服务器

于 2013-07-24T14:15:50.260 回答
19

在 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 对象。

于 2018-04-01T17:51:46.357 回答
7

Pärt Johanson 我希望我能发表评论只是为了感谢您将我从递归循环中解救出来,我整天都在扯头发,然后一遍又一遍地阅读(非常无用的)节点文档。找到你的答案后,我去挖掘文档,我什至找不到res.setEncoding任何地方记录的方法!它只是作为两个示例的一部分显示,其中他们称res.setEncoding('utf8');您在哪里找到这个或您是如何找到它的!?

由于我没有足够的声誉来发表评论,因此我至少会为我的答案贡献一些有用的东西:Pärt Johanson 的答案对我来说 100% 有效,我只是根据自己的需要对其进行了一些调整,因为我正在使用它来下载和使用nw.Window.get().evalNWBin()NWJS 0.36.4 / 节点 11.11.0评估托管在我的服务器上的脚本(并使用 nwjc 编译) :

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

编辑: PS 我发布这个以防万一有人想知道如何处理非二进制响应——我的实际代码更深入一些,并检查响应内容类型标头以解析 JSON(预期失败,即 400、401、403)或 HTML(意外失败,即 404 或 500)

于 2019-03-15T21:12:59.623 回答
6
  1. 不要调用setEncoding()方法,因为默认情况下不分配编码,流数据将作为Buffer对象返回
  2. 调用回调方法将值转换为Buffer.from()对象。on.datachunkBuffer
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'));
    });
});
于 2019-04-15T10:37:37.973 回答
2

和这里的其他人一样,我需要处理来自 Node.js HTTP 响应(又名http.IncomingMessage)的二进制数据块。

除了 Pärt Johanson 的答案及其变体之外,现有的答案都没有真正适用于我的 Electron 6 项目(在发布时与 Node.js 12.4.0 捆绑在一起) 。

response.on('data', ondata) 尽管如此,即使使用该解决方案,块总是作为string对象(而不是预期和期望的Buffer对象)到达处理程序。这导致了额外的转换Buffer.from(chunk, 'binary')。无论我是否使用response.setEncoding('binary')或明确指定二进制编码,我都在获取字符串response.setEncoding(null)

我设法获得原始Buffer块的唯一方法是将其传送到我提供自定义方法response的实例stream.Writablewrite

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 服务器上。因此,response.on('data')实际上对于我在上面的代码片段中使用的示例 URL 来说效果很好,并且它不需要流。虽然这是特定于服务器的,但这很奇怪,我正在进一步调查它。

于 2019-09-15T22:13:22.697 回答
2

这里的每个人都在正确的轨道上,但要解决问题,你不能打电话给.setEncoding()EVER。

如果您调用.setEncoding(),它将创建一个StringDecoder并将其设置为默认解码器。如果您尝试通过nullor undefined,那么它仍然会StringDecoder使用其默认解码器创建一个UTF-8. 即使你打电话.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功能只是为了方便。

于 2020-05-26T20:16:02.410 回答