yikart/AiToEarn · error · AppException
ResponseCode.S3DownloadFileFailed
ResponseCode.S3DownloadFileFailed
Error message
S3DownloadFileFailed
What it means
putObjectFromUrl downloads a remote file with fetch and uploads it to S3/OSS. If the object does not already exist (head failed) and the fetch returns a null body, the service throws AppException(ResponseCode.S3DownloadFileFailed).
Source
Thrown at project/aitoearn-backend/libs/ali-oss/src/ali-oss.service.ts:31
) {}
getClient(): OSS {
return this.client
}
async putObject(key: string, file: Buffer | string, options?: OSS.PutObjectOptions) {
return await this.client.put(key, file, options)
}
async putObjectFromUrl(url: string, objectPath: string) {
try {
await this.client.head(objectPath)
return { path: objectPath, exists: true }
}
catch {
const response = await fetch(url)
if (response.body === null) {
throw new AppException(ResponseCode.S3DownloadFileFailed)
}
const buffer = Buffer.from(await response.arrayBuffer())
const contentType = response.headers.get('content-type') || undefined
const options: OSS.PutObjectOptions = contentType ? { headers: { 'Content-Type': contentType } } : {}
await this.client.put(objectPath, buffer, options)
return { path: objectPath }
}
}
async getObject(key: string) {
return await this.client.get(key)
}
async deleteObject(key: string) {
return await this.client.delete(key)
}
async listObjects(query: OSS.ListObjectsQuery, options?: OSS.RequestOptions) {View on GitHub (pinned to d3aa8bea5b)
Solutions
- Log response.status before throwing; if it is 4xx the source URL is bad — fix or refresh the URL
- Retry with a fresh signed URL if the original expired
- Check egress network/DNS from the deployment environment (curl the URL from inside the container)
- Optionally check response.ok and include status text in a richer error
Example fix
// before
const response = await fetch(url)
if (response.body === null) throw new AppException(ResponseCode.S3DownloadFileFailed)
// after
const response = await fetch(url)
if (!response.ok || response.body === null) {
throw new AppException(ResponseCode.S3DownloadFileFailed, `status=${response.status}`)
} Defensive patterns
Strategy: retry
Validate before calling
const probe = await fetch(url, { method: 'HEAD' })
if (!probe.ok) throw new Error(`Source URL unreachable before upload: ${probe.status}`) Type guard
function isDownloadable(res: Response): boolean {
return res.ok && res.body !== null
} Try / catch
try {
await ossService.putObjectFromUrl(url, objectPath)
} catch (e) {
if (e.code === ResponseCode.S3DownloadFileFailed) {
await withBackoff(() => ossService.putObjectFromUrl(freshUrl(url), objectPath), 3)
} else throw e
} Prevention
- Pre-flight HEAD-check source URLs before queuing downloads
- Use fresh, unexpired signed URLs
- Verify egress network/DNS from the deployment environment
- Include HTTP status in logs/error messages when downloads fail
When it happens
Trigger: fetch(url) succeeds at HTTP level (or fails silently to produce a body) but response.body is null — e.g. 204/304 responses, redirects to empty bodies, or unreachable/blocked URLs resolved by the fetch polyfill.
Common situations: Source URL returns 404/410 but fetch config yields a null body, the remote host blocks the server's egress IP, DNS failure in a container, or an expired signed URL.
Related errors
- S3DownloadFileFailed
- ResponseCode.S3DownloadFileFailed
- Failed to fetch image: ${response.status} ${response.statusT
- Download failed: ${response.status}
- Relay uploadSign returned no uploadUrl: ${JSON.stringify(sig
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/5624c0ca53cc7f90.
Report an issue: GitHub.