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

  1. Log response.status before throwing; if it is 4xx the source URL is bad — fix or refresh the URL
  2. Retry with a fresh signed URL if the original expired
  3. Check egress network/DNS from the deployment environment (curl the URL from inside the container)
  4. 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

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


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/5624c0ca53cc7f90. Report an issue: GitHub.