Yuki Takei 6 месяцев назад
Родитель
Сommit
868820a9b0

+ 30 - 9
apps/app/src/server/service/file-uploader/aws/index.ts

@@ -179,15 +179,36 @@ class AwsFileUploader extends AbstractFileUploader {
     const filePath = getFilePathOnStorage(attachment);
     const contentHeaders = createContentHeaders(attachment);
 
-    await s3.send(new PutObjectCommand({
-      Bucket: getS3Bucket(),
-      Key: filePath,
-      Body: readable,
-      ACL: getS3PutObjectCannedAcl(),
-      // put type and the file name for reference information when uploading
-      ContentType: getContentHeaderValue(contentHeaders, 'Content-Type'),
-      ContentDisposition: getContentHeaderValue(contentHeaders, 'Content-Disposition'),
-    }));
+    try {
+      const uploadTimeout = configManager.getConfig('app:fileUploadTimeout');
+
+      await s3.send(
+        new PutObjectCommand({
+          Bucket: getS3Bucket(),
+          Key: filePath,
+          Body: readable,
+          ACL: getS3PutObjectCannedAcl(),
+          // put type and the file name for reference information when uploading
+          ContentType: getContentHeaderValue(contentHeaders, 'Content-Type'),
+          ContentDisposition: getContentHeaderValue(contentHeaders, 'Content-Disposition'),
+        }),
+        { abortSignal: AbortSignal.timeout(uploadTimeout) },
+      );
+
+      logger.debug(`File upload completed successfully: fileName=${attachment.fileName}`);
+    }
+    catch (error) {
+      // Handle timeout error specifically
+      if (error.name === 'AbortError') {
+        logger.warn(`Upload timeout: fileName=${attachment.fileName}`, error);
+      }
+      else {
+        logger.error(`File upload failed: fileName=${attachment.fileName}`, error);
+      }
+      // Re-throw the error to be handled by the caller.
+      // The pipeline automatically handles stream cleanup on error.
+      throw error;
+    }
   }
 
   /**

+ 26 - 7
apps/app/src/server/service/file-uploader/azure.ts

@@ -134,13 +134,32 @@ class AzureFileUploader extends AbstractFileUploader {
     const blockBlobClient: BlockBlobClient = containerClient.getBlockBlobClient(filePath);
     const contentHeaders = createContentHeaders(attachment);
 
-    await blockBlobClient.uploadStream(readable, undefined, undefined, {
-      blobHTTPHeaders: {
-        // put type and the file name for reference information when uploading
-        blobContentType: getContentHeaderValue(contentHeaders, 'Content-Type'),
-        blobContentDisposition: getContentHeaderValue(contentHeaders, 'Content-Disposition'),
-      },
-    });
+    try {
+      const uploadTimeout = configManager.getConfig('app:fileUploadTimeout');
+
+      await blockBlobClient.uploadStream(readable, undefined, undefined, {
+        blobHTTPHeaders: {
+          // put type and the file name for reference information when uploading
+          blobContentType: getContentHeaderValue(contentHeaders, 'Content-Type'),
+          blobContentDisposition: getContentHeaderValue(contentHeaders, 'Content-Disposition'),
+        },
+        abortSignal: AbortSignal.timeout(uploadTimeout),
+      });
+
+      logger.debug(`File upload completed successfully: fileName=${attachment.fileName}`);
+    }
+    catch (error) {
+      // Handle timeout error specifically
+      if (error.name === 'AbortError') {
+        logger.warn(`Upload timeout: fileName=${attachment.fileName}`, error);
+      }
+      else {
+        logger.error(`File upload failed: fileName=${attachment.fileName}`, error);
+      }
+      // Re-throw the error to be handled by the caller.
+      // The pipeline automatically handles stream cleanup on error.
+      throw error;
+    }
   }
 
   /**

+ 22 - 1
apps/app/src/server/service/file-uploader/local.ts

@@ -166,7 +166,28 @@ module.exports = function(crowi: Crowi) {
 
     const writeStream: Writable = fs.createWriteStream(filePath);
 
-    return pipeline(fileStream, writeStream);
+    try {
+      const uploadTimeout = configManager.getConfig('app:fileUploadTimeout');
+      await pipeline(
+        fileStream,
+        writeStream,
+        { signal: AbortSignal.timeout(uploadTimeout) },
+      );
+
+      logger.debug(`File upload completed successfully: fileName=${attachment.fileName}`);
+    }
+    catch (error) {
+      // Handle timeout error specifically
+      if (error.name === 'AbortError') {
+        logger.warn(`Upload timeout: fileName=${attachment.fileName}`, error);
+      }
+      else {
+        logger.error(`File upload failed: fileName=${attachment.fileName}`, error);
+      }
+      // Re-throw the error to be handled by the caller.
+      // The pipeline automatically handles stream cleanup on error.
+      throw error;
+    }
   };
 
   lib.saveFile = async function({ filePath, contentType, data }) {