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:
+32
-5
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user