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 = () => {
|
const getS3Client = () => {
|
||||||
if (!s3ClientInstance) {
|
if (!s3ClientInstance) {
|
||||||
|
const endpoint = String(config.S3_ENDPOINT || '').replace(/\/$/, '');
|
||||||
s3ClientInstance = new S3Client({
|
s3ClientInstance = new S3Client({
|
||||||
endpoint: config.S3_ENDPOINT,
|
endpoint,
|
||||||
region: config.S3_REGION,
|
region: config.S3_REGION || 'us-east-1',
|
||||||
credentials: {
|
credentials: {
|
||||||
accessKeyId: config.S3_ACCESS_KEY,
|
accessKeyId: config.S3_ACCESS_KEY,
|
||||||
secretAccessKey: config.S3_SECRET_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;
|
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') => {
|
const uploadToTempBucket = async (fileBuffer, filename, contentType = 'application/octet-stream') => {
|
||||||
try {
|
try {
|
||||||
const client = getS3Client();
|
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}`);
|
logger.info(`[S3 Storage] Uploaded temp file: ${filename} to bucket ${config.S3_TEMP_BUCKET}`);
|
||||||
return { tempFileName: filename, bucket: config.S3_TEMP_BUCKET };
|
return { tempFileName: filename, bucket: config.S3_TEMP_BUCKET };
|
||||||
} catch (error) {
|
} 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') {
|
if (config.NODE_ENV === 'test' || error.code === 'ECONNREFUSED') {
|
||||||
logger.warn(`[S3 Storage MOCK] Simulated temp upload for ${filename}`);
|
logger.warn(`[S3 Storage MOCK] Simulated temp upload for ${filename}`);
|
||||||
return { tempFileName: filename, bucket: config.S3_TEMP_BUCKET };
|
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}`);
|
logger.info(`[S3 Storage] Committed ${tempFilename} → ${targetBucket}/${targetKey}`);
|
||||||
return { fileKey: targetKey, bucket: targetBucket, fileUrl };
|
return { fileKey: targetKey, bucket: targetBucket, fileUrl };
|
||||||
} catch (error) {
|
} 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') {
|
if (config.NODE_ENV === 'test' || error.code === 'ECONNREFUSED') {
|
||||||
logger.warn(`[S3 Storage MOCK] Simulated file commit for ${targetKey}`);
|
logger.warn(`[S3 Storage MOCK] Simulated file commit for ${targetKey}`);
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user