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 社区有优化或替代解决方案,我会更新我的答案。
我正在使用 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 社区有优化或替代解决方案,我会更新我的答案。