Fetching images with node - Error: read ECONNRESET at TLSWrap.onStreamRead

Fetching images with node - Error: read ECONNRESET at TLSWrap.onStreamRead

我正在使用 node-fetch and canvas 使用 nodejs 获取和处理图像。到目前为止,一切进展顺利。我有一系列图像 urls,我使用 Promise.all:

并行获取它们
import { loadImage } from 'canvas';

await Promise.all<CanvasImageSource>(
  urls.map((url: string) => loadImage(url)) // <--- return array of promises
)
  .then((images: CanvasImageSource[]): void => {
     // do some stuff with the images
  })
  .catch((e) => { throw e });

效果很好。但是昨晚我尝试了我想使用的某个图像源 url,但出现以下错误:

Error: read ECONNRESET
    at TLSWrap.onStreamRead (internal/stream_base_commons.js:205:27)
---------------------------------------------
    at TLSSocket.Readable.on (_stream_readable.js:838:35)
    at tickOnSocket (_http_client.js:696:10)
    at onSocketNT (_http_client.js:747:5)
    at processTicksAndRejections (internal/process/task_queues.js:84:21)
---------------------------------------------
    at ClientRequest.onSocket (_http_client.js:735:11)
    at setRequestSocket (_http_agent.js:396:7)
    at handleSocketCreation_Inner (_http_agent.js:389:7)
    at oncreate (_http_agent.js:262:5)
    at Agent.createSocket (_http_agent.js:267:5)
    at Agent.addRequest (_http_agent.js:224:10)
    at new ClientRequest (_http_client.js:296:16)
    at Object.request (https.js:314:10)
    at simpleGet ../../etc

我阅读了如何调试Node.js中的错误ECONNRESET? ,那里的答案似乎表明这是服务器端的错误。但是,当我打印要传递给 loadImage 的 urls,然后在浏览器中访问它们时,我能够在浏览器中很好地取回图像。其中一个 url 是

https://apps.fs.usda.gov/arcx/rest/services/RDW_Wildfire/ProbabilisticWildfireRisk/MapServer/export?bbox=-13795354.864908611%2C6095394.383573096%2C-13785570.92528811%2C6085610.443952593&size=256%2C256&format=png32&bboxSR=102100&imageSR=102100&f=image&layers=show%3A0

这是美国林业部的GIS栅格影像服务。去那个url是我的浏览器returns图片没问题。

我想也许我可能一次用太多请求炸毁了他们的服务器,因为 urls 的数组通常有 6-10 个图像 urls 在它里面用于任何给定的调用运行此代码的函数,但我将 url 的数量减少到 1,以便只发出 1 个请求,但没有变化。仍然是一个错误。在浏览器中访问这些 url 时我注意到的一件事是响应有点慢。这可能与它有关吗?

一个类似(但不相同)的政府图像服务 url 使用此代码工作得很好,可以毫无问题地并行检索许多图像。一个有效的示例 url 是:

https://landfire.cr.usgs.gov/arcgis/rest/services/Landfire/US_200/MapServer/export?bbox=-13814922.744149616%2C6095394.383573096%2C-13805138.804529114%2C6085610.443952593&size=256%2C256&format=png32&bboxSR=102100&imageSR=102100&f=image&layers=show%3A25

(编辑:landfire 服务器现在已关闭,因此 url 在它们恢复之前无法工作)

我尝试按照另一个问题中的建议使用 longjohn 来获得更详细的错误打印输出,但我的代码中的 import 'longjohn' 似乎没有任何改变。

为什么这些新的 url 会在节点中抛出此错误,而不是在浏览器中?我知道这个主题还有其他问题,但它们似乎没有帮助我调试我的具体问题。

更进一步 - 使用答案

@DipakC 写了一个很好的答案,它利用 axios 来获取图像。使用他的 downloadImage,我能够获取图像作为 Promise.all 的一部分,如下所示:

await Promise.all(
  tilenames.map((tilename: string) => {
    const url = `${baseurl}/${tilename}.png` // baseurl is remote esri url
    return downloadImage({ url });
  })
);

最终,我需要将这些图像转换为 ImageData,以便可以使用它们的像素数据。这可以使用一些 canvas 方法轻松实现。目前,我使用上面的代码,然后重用我的原始代码,但是这次,我没有使用远程 urls 作为图像,而是使用我刚刚下载的图像的路径名:

// immediately after the Promise.all above:

await Promise.all(
  tilenames.map((tilename: string) => {
    const url = `${localpath}/${tilename}.png` // localpath is where image was downloaded to
    return loadImage(url);
  })
)
  .then((images: Image[]) => {
    const canvas: Canvas = createCanvas(256, 256);
    const ctx: RenderingContext = canvas.getContext('2d');
    ctx.drawImage(image, 0, 0, 256, 256);
    const imageData = ctx.getImageData(0, 0, 256, 256);
    // do something with imageData, more processing
  });

问题: 有什么办法可以避免这两个阶段的过程吗?意思是,我可以不使用 downloadImage 将图像下载为文件,而是将其图像数据直接写入变量,就像我在第二步中使用 loadImage?

所做的那样

附录:

这是来自 node-canvas src code

的 'loadImage' 的源代码
function loadImage (src) {
  return new Promise((resolve, reject) => {
    const image = new Image()

    function cleanup () {
      image.onload = null
      image.onerror = null
    }

    image.onload = () => { cleanup(); resolve(image) }
    image.onerror = (err) => { cleanup(); reject(err) }

    image.src = src
  })
}

在查看了您的问题后,我对您添加的来源 URL 进行了一些研发,以下是从下面 URL.

获取图像的可能解决方案

方案一:

下面的代码完全可以正常工作,有无穷无尽的请求。我查看了实际的 usda.gov 网站并查看了 headers 和回复。我使用了 Axios 并验证了这里收到的响应 in-stream。我可能不确定 canvas 它是否会处理流响应,但是 Axios 工作正常。

使用 Axios 的原因是我也尝试过 canvas 以某种方式下载图像失败。在研究了实际的应用程序和有效负载并尝试使用 Axios 后,它工作正常并且发现最兼容。

const axios = require("axios");
const fs = require("fs");
const path = require("path");

/**
 * @description create image directory if not exists
 * @param {String} fPath
 * @returns
 */
async function createDirIfNotExists(fPath) {
  return new Promise((resolve, reject) => {
    if (fs.existsSync(fPath)) {
      console.error("Directory has already created");
      resolve();
    } else {
      fs.mkdirSync(fPath);
      resolve();
    }
  });
}

/**
 * @description download the image
 * @param {Object} requestBody
 * @returns
 */
async function downloadImage(requestBody) {
  const fPath = path.resolve(__dirname, "images");
  await createDirIfNotExists(fPath);
  const writer = fs.createWriteStream(`${fPath}/code.png`);
  const streamResponse = await axios(requestBody).catch((err) => {
    console.error(err);
  });
  streamResponse.data.pipe(writer);
  return new Promise((resolve, reject) => {
    writer.on("finish", resolve("success"));
    writer.on("error", reject("error"));
  });
}

const requestBody = {
  method: "get",
  url: "https://apps.fs.usda.gov/arcx/rest/services/RDW_Wildfire/ProbabilisticWildfireRisk/MapServer/export",
  headers: {},
  params: {
    bbox: "-13795354.864908611,6095394.383573096,-13785570.92528811,6085610.443952593",
    bboxSR: 102100,
    layers: "",
    layerDefs: "",
    size: "256,256",
    imageSR: 102100,
    historicMoment: "",
    format: "png",
    transparent: false,
    dpi: "",
    time: "",
    layerTimeOptions: "",
    dynamicLayers: "",
    gdbVersion: "",
    mapScale: "",
    rotation: "",
    datumTransformations: "",
    layerParameterValues: "",
    mapRangeValues: "",
    layerRangeValues: "",
    f: "image",
  },
  responseType: "stream",
};

(async () => {
  const response = await downloadImage(requestBody).catch(() => {
    console.error("Something went wrong while downloading file");
    return false;
  });
  if (response === "success") {
    console.info("File downloaded succesfully");
  }
})();

希望我的回答能帮助您解决问题。但如果您仍然面临同样的问题,请告诉我。此代码在本地机器以及 repl.it.

中进行了测试

============================================= =======================

方案二:

在查看了您更新后的描述后,我也更新了我的解决方案并将更有效的解决方案作为解决方案 2 添加到答案中。

解决方案 2 读取图像流缓冲区并将其转换为 Uint8ClampedArray,其格式可能类似于 returns 而使用 ctx.getImageData() 函数。我还将 responseType 更改为 arraybuffer

因此,此解决方案解决了问题中列出的两个问题,即您不需要下载图像以及接收图像数据 Uint8ClampedArray 格式。

const axios = require("axios");
const fs = require("fs");

/**
 * @description create image directory if not exists
 * @param {String} fPath
 * @returns
 */
async function createDirIfNotExists(fPath) {
  return new Promise((resolve, reject) => {
    if (fs.existsSync(fPath)) {
      console.error("Directory has already created");
      resolve();
    } else {
      fs.mkdirSync(fPath);
      resolve();
    }
  });
}

/**
 * @description get the image data directly from buffer.
 * @param {Object} requestBody
 * @returns
 */
async function getImageData(requestBody) {
  try {
    const { format } = requestBody.params;
    const arrFormat = {
      png: "image/png",
      jpg: "image/jpeg",
      jpeg: "image/jpeg",
      gif: "image/gif",
    };
    const streamResponse = await axios(requestBody).catch((err) => {
      console.error(err);
    });
    // store the streamed data
    const bufferData = streamResponse.data;
    // convert into in base64 format
    const base64Data = `data:${arrFormat[format]};base64,${Buffer.from(
      bufferData
    ).toString("base64")}`;
    // convert into in Uint8ClampedArray (as I referred from the code ctx.getImageData() return Uint8ClampedArray)
    const imageData = new Uint8ClampedArray(bufferData);
    return {
      base64Data,
      imageData,
    };
  } catch (err) {
    console.error(err);
  }
}

const requestBody = {
  method: "get",
  url: "https://apps.fs.usda.gov/arcx/rest/services/RDW_Wildfire/ProbabilisticWildfireRisk/MapServer/export",
  headers: {},
  params: {
    bbox: "-13795354.864908611,6095394.383573096,-13785570.92528811,6085610.443952593",
    bboxSR: 102100,
    layers: "",
    layerDefs: "",
    size: "256,256",
    imageSR: 102100,
    historicMoment: "",
    format: "png",
    transparent: false,
    dpi: "",
    time: "",
    layerTimeOptions: "",
    dynamicLayers: "",
    gdbVersion: "",
    mapScale: "",
    rotation: "",
    datumTransformations: "",
    layerParameterValues: "",
    mapRangeValues: "",
    layerRangeValues: "",
    f: "image",
  },
  // responseType: "stream",
  responseType: "arraybuffer",
};

(async () => {
  const response = await getImageData(requestBody).catch(() => {
    console.error("Something went wrong while downloading file");
    return false;
  });
  console.info("image data received", response);
})();

希望此解决方案 2 可以解决您的问题。

当您想应用复杂的图像处理或处理时,我建议使用队列。这可能是使用内存和 CPU.

的有效方式

让我知道还面临一个issue.Whosebug

如果 Whosebug 社区有优化或替代解决方案,我会更新我的答案。