{"record":{"id":"8602758953275846","repo":"AlistGo/alist","slug":"faild-to-get-etag-from-header","errorCode":null,"errorMessage":"faild to get ETag from header","messagePattern":"faild to get ETag from header","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"drivers/cloudreve/util.go","lineNumber":414,"sourceCode":"\t\treq.ContentLength = byteSize\n\t\tfinish += byteSize\n\t\tres, err := base.HttpClient.Do(req)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tetag := res.Header.Get(\"ETag\")\n\t\tres.Body.Close()\n\t\tswitch {\n\t\tcase res.StatusCode != 200:\n\t\t\tretryCount++\n\t\t\tif retryCount > maxRetries {\n\t\t\t\treturn fmt.Errorf(\"upload failed after %d retries due to server errors, error %d\", maxRetries, res.StatusCode)\n\t\t\t}\n\t\t\tbackoff := time.Duration(1<<retryCount) * time.Second\n\t\t\tutils.Log.Warnf(\"[Cloudreve-S3] server errors %d while uploading, retrying after %v...\", res.StatusCode, backoff)\n\t\t\ttime.Sleep(backoff)\n\t\tcase etag == \"\":\n\t\t\treturn errors.New(\"faild to get ETag from header\")\n\t\tdefault:\n\t\t\tretryCount = 0\n\t\t\tetags = append(etags, etag)\n\t\t\tfinish += byteSize\n\t\t\tup(float64(finish) * 100 / float64(stream.GetSize()))\n\t\t\tchunk++\n\t\t}\n\t}\n\n\t// s3LikeFinishUpload\n\t// https://github.com/cloudreve/frontend/blob/b485bf297974cbe4834d2e8e744ae7b7e5b2ad39/src/component/Uploader/core/api/index.ts#L204-L252\n\tbodyBuilder := &strings.Builder{}\n\tbodyBuilder.WriteString(\"<CompleteMultipartUpload>\")\n\tfor i, etag := range etags {\n\t\tbodyBuilder.WriteString(fmt.Sprintf(\n\t\t\t`<Part><PartNumber>%d</PartNumber><ETag>%s</ETag></Part>`,\n\t\t\ti+1, // PartNumber 从 1 开始\n\t\t\tetag,","sourceCodeStart":396,"sourceCodeEnd":432,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/cloudreve/util.go#L396-L432","documentation":"In Cloudreve S3-like policy upload, a chunk PUT returned HTTP 200 but the response had no ETag header, so the driver cannot assemble the multipart completion manifest. ETags are required to complete an S3 multipart upload.","triggerScenarios":"upS3-like loop: res.Header.Get(\"ETag\") == \"\" with StatusCode 200. Happens when the S3-compatible backend (or a proxy/CDN in front of it) omits ETag on PutObject/UploadPart responses.","commonSituations":"Non-AWS S3 clones (some MinIO gateway modes, certain object storage vendors) that strip or never set ETag; proxy (nginx/Cloudflare) dropping the header; servers that only return ETag when the request supplies Content-MD5.","solutions":["Verify with a manual multipart PUT (curl -X PUT with -D -) whether the endpoint really returns ETag; if not, switch the Cloudrebve storage policy to a provider that does.","Remove any proxy/CDN layer in front of the S3 endpoint that could strip the ETag response header.","Update the S3-compatible server (MinIO/Ceph) to a version known to return ETag for UploadPart.","As a workaround, use a different upload policy (local or OneDrive) in Cloudreve for this storage."],"exampleFix":"# verify ETag presence on chunk upload\ncurl -sS -D - -o /dev/null -X PUT \\\n  -H 'Authorization: ...' \\\n  --data-binary @chunk.bin \\\n  'https://s3.example.com/bucket/key?partNumber=1&uploadId=...'\n# -> HTTP/1.1 200 OK\n# -> ETag: \"abc123...\"   (must be present)","handlingStrategy":"validation","validationCode":"// before switching a policy to S3-like, confirm the backend returns ETag on part PUT\nresp, err := http.NewRequest(http.MethodPut, partURL, bytes.NewReader(chunk))\n// ... execute ...\nif resp.Header.Get(\"ETag\") == \"\" {\n    return errors.New(\"backend strips ETag; multipart completion will fail — pick another endpoint\")\n}","typeGuard":null,"tryCatchPattern":"etag := res.Header.Get(\"ETag\")\nif etag == \"\" {\n    if sc := res.Header.Get(\"X-Amz-Etag\"); sc != \"\" { etag = sc } // some clones use alt casing/keys\n}\nif etag == \"\" {\n    return errors.New(\"faild to get ETag from header\")\n}","preventionTips":["Verify ETag presence with curl before adopting an S3-compatible backend","Keep proxies off the S3 data path so headers survive","Prefer mainstream S3 providers for multipart policies"],"tags":["cloudreve","s3","multipart-upload","etag","go"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}