feat: proxy GetObject from Telegram CDN for real S3 client compatibility
- Add proxyS3Get config (PROXY_S3_GET env, default true) to env.ts - Proxy handleGetObject and handleGetMultipartObject: fetch from Telegram CDN and return 200 with streaming body instead of 302 redirect - Real S3 clients (AWS SDK v3) expect 200+body on GetObject, not redirect - Legacy 302 redirect path preserved when proxyS3Get=false - Updated production-e2e: GetObject asserts 200 with body content - Updated s3-sdk.test.ts: GetObject asserts 200 with body (removed try/catch) - Cleaned up unused multipart imports in s3-sdk.test.ts All 49 tests pass (29 production-e2e + 20 s3-sdk).
This commit is contained in:
@@ -19,6 +19,7 @@ interface AppConfig {
|
||||
s3AccessKey: string;
|
||||
s3SecretKey: string;
|
||||
s3DefaultRegion: string;
|
||||
proxyS3Get: boolean;
|
||||
}
|
||||
|
||||
const requiredEnv = {
|
||||
@@ -78,6 +79,7 @@ export const config: AppConfig = {
|
||||
s3AccessKey: process.env.S3_ACCESS_KEY || 'teleuploader-admin',
|
||||
s3SecretKey: process.env.S3_SECRET_KEY || '',
|
||||
s3DefaultRegion: process.env.S3_DEFAULT_REGION || 'us-east-1',
|
||||
proxyS3Get: process.env.PROXY_S3_GET !== 'false',
|
||||
};
|
||||
|
||||
logger.info('Environment variables loaded', {
|
||||
|
||||
+69
-6
@@ -313,11 +313,43 @@ const handleGetObject = async (
|
||||
const fileInfo = await getFileInfo(file.telegramFileId);
|
||||
const redirectUrl = `https://api.telegram.org/file/bot${fileInfo.bot_token}/${fileInfo.file_path}`;
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
// Proxy the content from Telegram CDN so real S3 clients get the body directly.
|
||||
if (!config.proxyS3Get) {
|
||||
// Legacy 302 redirect path (when proxy is disabled)
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: { Location: redirectUrl, 'x-amz-request-id': reqId },
|
||||
});
|
||||
}
|
||||
|
||||
const tgResponse = await fetch(redirectUrl);
|
||||
if (!tgResponse.ok) {
|
||||
logger.warn('Telegram content fetch failed', {
|
||||
status: tgResponse.status,
|
||||
fileId: file.telegramFileId,
|
||||
});
|
||||
return s3ErrorResponse(
|
||||
'InternalError',
|
||||
'Failed to fetch object content from storage',
|
||||
`/${bucket}/${key}`,
|
||||
502,
|
||||
reqId,
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(tgResponse.body, {
|
||||
status: 200,
|
||||
headers: {
|
||||
Location: redirectUrl,
|
||||
'content-type': file.mimeType,
|
||||
'content-length': String(file.sizeBytes),
|
||||
etag: `"${file.fileHash || ''}"`,
|
||||
'last-modified':
|
||||
file.createdAt instanceof Date
|
||||
? file.createdAt.toUTCString()
|
||||
: new Date(file.createdAt).toUTCString(),
|
||||
'x-amz-request-id': reqId,
|
||||
'accept-ranges': 'bytes',
|
||||
'cache-control': 'public, max-age=31536000',
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -344,11 +376,42 @@ const handleGetMultipartObject = async (
|
||||
const fileInfo = await getFileInfo(parts[0].telegramFileId);
|
||||
const redirectUrl = `https://api.telegram.org/file/bot${fileInfo.bot_token}/${fileInfo.file_path}`;
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
if (!config.proxyS3Get) {
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: { Location: redirectUrl, 'x-amz-request-id': reqId },
|
||||
});
|
||||
}
|
||||
|
||||
const tgResponse = await fetch(redirectUrl);
|
||||
if (!tgResponse.ok) {
|
||||
logger.warn('Telegram multipart content fetch failed', {
|
||||
status: tgResponse.status,
|
||||
uploadId: file.multipartUploadId,
|
||||
});
|
||||
return s3ErrorResponse(
|
||||
'InternalError',
|
||||
'Failed to fetch object content from storage',
|
||||
`/${bucket}/${key}`,
|
||||
502,
|
||||
reqId,
|
||||
);
|
||||
}
|
||||
|
||||
const totalSize = parts.reduce((sum, p) => sum + p.sizeBytes, 0);
|
||||
return new Response(tgResponse.body, {
|
||||
status: 200,
|
||||
headers: {
|
||||
Location: redirectUrl,
|
||||
'content-type': file.mimeType,
|
||||
'content-length': String(totalSize || file.sizeBytes),
|
||||
etag: `"${file.fileHash || ''}"`,
|
||||
'last-modified':
|
||||
file.createdAt instanceof Date
|
||||
? file.createdAt.toUTCString()
|
||||
: new Date(file.createdAt).toUTCString(),
|
||||
'x-amz-request-id': reqId,
|
||||
'accept-ranges': 'bytes',
|
||||
'cache-control': 'public, max-age=31536000',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user