{"record":{"id":"90c34005889d353e","repo":"AlistGo/alist","slug":"faild-to-get-etag-from-header-90c340","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_v4/util.go","lineNumber":431,"sourceCode":"\t\treq = req.WithContext(ctx)\n\t\treq.ContentLength = 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\", maxRetries)\n\t\t\t}\n\t\t\tbackoff := time.Duration(1<<retryCount) * time.Second\n\t\t\tutils.Log.Warnf(\"server error %d, 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(file.GetSize()))\n\t\t\tchunk++\n\t\t}\n\t}\n\n\t// s3LikeFinishUpload\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,\n\t\t))","sourceCodeStart":413,"sourceCodeEnd":449,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/cloudreve_v4/util.go#L413-L449","documentation":"CloudreveV4 S3-like upload: a chunk PUT returned 200 but no ETag response header, so the multipart completion manifest cannot be built and the driver errors out. Same root pattern as the V3 S3 driver's ETag check, on the V4 callback path.","triggerScenarios":"upS3-like loop: res.Header.Get(\"ETag\") == \"\" with a 200 status. Produced by S3-compatible backends or intermediary proxies that omit ETag on UploadPart/PutObject responses.","commonSituations":"Exotic S3 clones that skip ETag; nginx/CDN stripping the header; gateway-mode MinIO in front of another store; header case/encoding quirks in nonstandard implementations.","solutions":["Verify with curl -X PUT -D - that the endpoint returns an ETag on part upload.","Remove header-stripping proxies from the path to the S3 endpoint.","Upgrade the S3-compatible server to a version that emits ETag for multipart parts.","Switch the Cloudrebve V4 policy to local/OneDrive storage if the backend cannot be fixed."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"etag := res.Header.Get(\"ETag\")\nif etag == \"\" { etag = res.Header.Get(\"X-Amz-Etag\") } // tolerate alt headers\nif etag == \"\" { return errors.New(\"faild to get ETag from header\") }","typeGuard":null,"tryCatchPattern":"if err := d.upS3Like(...); err != nil {\n    if err.Error() == \"faild to get ETag from header\" {\n        // backend is dropping ETag; no point retrying — switch endpoint/policy\n        return errors.New(\"S3 backend omits ETag on part upload; use an ETag-compliant endpoint\")\n    }\n    return err\n}","preventionTips":["Validate ETag presence with a curl PUT before adopting a backend","Bypass header-stripping proxies on the S3 data path","Keep the S3-compatible server current"],"tags":["cloudreve-v4","s3","multipart-upload","etag","go"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}