我创建了一个函数,它接受存储在 Firebase 存储中的图像的 URL 字符串数组,该函数成功地适用于总下载量小于 1GB 的实例。
但是,随着数量的增加,我看到的最后一个控制台日志是:
“所有图像均传输至 zip”
然后好几秒钟什么也没有发生,我得到:
函数执行花费了 141568 毫秒,完成状态:“崩溃”
然后:
**错误:读取 ECONNRESET 在 TLSWrap.onStreamRead (节点:内部/stream_base_commons:217:20) 在 TLSWrap.callbackTrampoline (节点:内部/async_hooks:130:17)**
我在互联网上寻找解决方案,但我只能找到 2017 年的解决方案,人们建议 google-cloud-node 库对套接字的处理以及云功能环境中的默认套接字超时已得到修复。
这是我的云功能:
const functions = require('firebase-functions');
const fetch = require('node-fetch');
const archiver = require('archiver');
const fs = require('fs');
const path = require('path');
const os = require('os');
const admin = require('firebase-admin');
const { Storage } = require('@google-cloud/storage');
admin.initializeApp({
credential: admin.credential.applicationDefault(),
storageBucket: process.env.FIREBASE_STORAGE_BUCKET
});
const runtimeOpts = {
timeoutSeconds: 300,
memory: '8GB'
};
exports.batchDownload = functions
.runWith(runtimeOpts)
.https.onRequest(async (req, res) => {
console.log('Function started');
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Methods', 'POST');
res.set('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
console.log('OPTIONS request received');
res.status(204).send('');
return;
}
const imageUrls = req.body.imageUrls;
const inspectionId = req.body.id;
if (!Array.isArray(imageUrls)) {
console.log('Invalid request format');
res.status(400).send('Invalid request: incorrect data format');
return;
}
const tempDir = path.join(os.tmpdir(), 'images');
const zipPath = path.join(os.tmpdir(), 'images.zip');
if (!fs.existsSync(tempDir)) {
console.log('Creating temporary directory');
fs.mkdirSync(tempDir);
}
try {
// Create zip archive
const output = fs.createWriteStream(zipPath);
const archive = archiver('zip', {
zlib: { level: 9 },
});
archive.pipe(output);
// Stream images directly to the zip file
await Promise.all(imageUrls.map(async (url, index) => {
console.log(`Streaming image from ${url}`);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch image from ${url}`);
}
const stream = response.body;
const filePath = `image${index}.jpg`;
archive.append(stream, { name: filePath });
}));
console.log('All images streamed to zip');
// Finalize the archive
await archive.finalize();
// Upload zip file to Cloud Storage
const storage = new Storage();
const bucket = storage.bucket(process.env.FIREBASE_STORAGE_BUCKET);
const file = bucket.file(`inspections/${inspectionId}/images.zip`);
const stream = file.createWriteStream({
metadata: {
contentType: 'application/zip',
},
});
await new Promise((resolve, reject) => {
stream.on('error', reject);
stream.on('finish', resolve);
fs.createReadStream(zipPath).pipe(stream);
});
console.log('Zip file uploaded to Cloud Storage');
// Generate download URL
const config = {
action: 'read',
expires: Date.now() + 60 * 60 * 1000, // Set the expiration to 1 hour from now
};
const [url] = await file.getSignedUrl(config);
console.log('Generated download URL:', url);
// Send response with download URL
res.status(200).send({ downloadUrl: url });
} catch (error) {
console.error('Error during batch download:', error);
res.status(500).send('Error during batch download');
} finally {
// Cleanup temporary files
fs.rmdirSync(tempDir, { recursive: true });
fs.unlinkSync(zipPath);
}
});
这是我的前端异步函数:
const downloadAllImages = async () => {
if (imagesToDownload.length < 1) {
return;
}
const imageUrls = imagesToDownload.map(image => image.downloadURL);
const zipFilePath = `inspections/${inspection.id}/images.zip`;
try {
const fileExists = await checkIfFileExists(zipFilePath);
if (!fileExists) {
// If the file does not exist, call the Cloud Function to generate it
setIsDownloading(true);
const response = await fetch(CLOUD_FUNCTION_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ imageUrls, id: inspection.id }),
});
if (!response.ok) {
throw new Error(`Failed to initiate image download: ${response.statusText}`);
}
const data = await response.json();
console.log("reponse", data)
const downloadUrl = data.downloadUrl;
const a = document.createElement('a');
a.href = downloadUrl;
a.download = 'images.zip';
a.click();
setShowDownloadAllImagesModal(false);
showNotification("Images Downloaded!");
setIsDownloading(false);
} else {
const storage = getStorage();
const zipRef = ref(storage, zipFilePath);
getDownloadURL(zipRef)
.then((url) => {
const a = document.createElement('a');
a.href = url;
a.download = 'images.zip';
a.click();
})
.catch((error) => {
console.error('Error downloading file:', error);
showNotification(`Error downloading file: ${error}`);
});
setShowDownloadAllImagesModal(false);
showNotification("Images Downloaded!");
setIsDownloading(false);
}
} catch (error) {
console.error(`Error checking file existence or initiating image download: ${error}`);
setShowDownloadAllImagesModal(false);
showNotification(`${error}`);
setIsDownloading(false);
}
};
我已将其设置为第一代云功能,它可以在总计小于 1GB 的实例中运行,但如果我尝试 3GB - 9GB 的图像文件,我会遇到 ECONNRESET 问题。
有人可以提供任何见解或建议进一步排除故障吗?
感谢您花时间阅读本文。
Google 云功能曾经宣传过具有 256mb 临时存储的硬限制,尽管我现在找不到任何具体参考。我想您正在使该功能崩溃,因为 /tmp 目录超出了允许的最大大小。
考虑改变你的方法,你可以使用 google-cloud/storage 包将下载直接流式传输到谷歌云存储。或者只是通过对 url 数组进行分块,将您的下载分块为更小、更易于管理的批次。