fix: disable AWS SDK checksum defaults for SeaweedFS uploads

PutObject was failing with XML parse errors because flexible checksum headers are unsupported; match SeaweedFS recommended S3 client settings.
This commit is contained in:
2026-08-15 03:00:42 +03:30
parent 7394829d49
commit aa2132f02c
+32 -5
View File
@@ -43,19 +43,45 @@ const buildPublicUrl = (bucketName, fileKey) => {
const getS3Client = () => {
if (!s3ClientInstance) {
const endpoint = String(config.S3_ENDPOINT || '').replace(/\/$/, '');
s3ClientInstance = new S3Client({
endpoint: config.S3_ENDPOINT,
region: config.S3_REGION,
endpoint,
region: config.S3_REGION || 'us-east-1',
credentials: {
accessKeyId: config.S3_ACCESS_KEY,
secretAccessKey: config.S3_SECRET_KEY
},
forcePathStyle: config.S3_FORCE_PATH_STYLE
forcePathStyle: config.S3_FORCE_PATH_STYLE !== false,
// SeaweedFS / MinIO: AWS SDK v3 default flexible checksums break PutObject
// (XML parse / unexpected content). Only checksum when the API requires it.
// See: https://github.com/seaweedfs/seaweedfs/wiki/nodejs-with-Seaweed-S3
requestChecksumCalculation: 'WHEN_REQUIRED',
responseChecksumValidation: 'WHEN_REQUIRED',
useDualstackEndpoint: false
});
logger.info(`[S3 Storage] Client ready → ${endpoint} (pathStyle=${config.S3_FORCE_PATH_STYLE !== false})`);
}
return s3ClientInstance;
};
const logS3Error = (label, error) => {
const status = error?.$metadata?.httpStatusCode;
const raw = error?.$response;
let bodyPreview = '';
try {
const body = raw?.body || raw?.reason || '';
if (typeof body === 'string') bodyPreview = body.slice(0, 300);
else if (body && typeof body.toString === 'function') bodyPreview = String(body).slice(0, 300);
} catch {
bodyPreview = '';
}
logger.error(
`[S3 Storage ERROR] ${label}: ${error.message}`
+ (status ? ` (HTTP ${status})` : '')
+ (bodyPreview ? ` body=${bodyPreview.replace(/\s+/g, ' ')}` : '')
);
};
const uploadToTempBucket = async (fileBuffer, filename, contentType = 'application/octet-stream') => {
try {
const client = getS3Client();
@@ -70,7 +96,8 @@ const uploadToTempBucket = async (fileBuffer, filename, contentType = 'applicati
logger.info(`[S3 Storage] Uploaded temp file: ${filename} to bucket ${config.S3_TEMP_BUCKET}`);
return { tempFileName: filename, bucket: config.S3_TEMP_BUCKET };
} catch (error) {
logger.error(`[S3 Storage ERROR] Temp upload failed for ${filename}: ${error.message}`);
logS3Error(`Temp upload failed for ${filename}`, error);
// Mock fallback for test environment when S3 is unavailable
if (config.NODE_ENV === 'test' || error.code === 'ECONNREFUSED') {
logger.warn(`[S3 Storage MOCK] Simulated temp upload for ${filename}`);
return { tempFileName: filename, bucket: config.S3_TEMP_BUCKET };
@@ -109,7 +136,7 @@ const commitTempFile = async (tempFilename, destinationKey = null, targetBucketK
logger.info(`[S3 Storage] Committed ${tempFilename}${targetBucket}/${targetKey}`);
return { fileKey: targetKey, bucket: targetBucket, fileUrl };
} catch (error) {
logger.error(`[S3 Storage ERROR] Failed to commit temp file ${tempFilename}: ${error.message}`);
logS3Error(`Failed to commit temp file ${tempFilename}`, error);
if (config.NODE_ENV === 'test' || error.code === 'ECONNREFUSED') {
logger.warn(`[S3 Storage MOCK] Simulated file commit for ${targetKey}`);
return {