Browse Source

handle part number

pull/7481/head
chrislu 3 weeks ago
parent
commit
8c62b44c04
  1. 90
      weed/s3api/s3api_object_handlers.go

90
weed/s3api/s3api_object_handlers.go

@ -430,6 +430,49 @@ func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request)
} }
entryFetchTime = time.Since(tEntryFetch) entryFetchTime = time.Since(tEntryFetch)
// Check if PartNumber query parameter is present (for multipart GET requests)
partNumberStr := r.URL.Query().Get("partNumber")
if partNumberStr == "" {
partNumberStr = r.URL.Query().Get("PartNumber")
}
// If PartNumber is specified, set headers and modify Range to read only that part
// This replicates the filer handler logic
if partNumberStr != "" {
if partNumber, parseErr := strconv.Atoi(partNumberStr); parseErr == nil && partNumber > 0 {
// Validate part number (1-based)
if partNumber > len(objectEntryForSSE.Chunks) {
glog.Warningf("GetObject: Invalid part number %d, object has %d chunks", partNumber, len(objectEntryForSSE.Chunks))
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidPart)
return
}
// Set parts count header (use actual chunk count like filer does)
w.Header().Set(s3_constants.AmzMpPartsCount, strconv.Itoa(len(objectEntryForSSE.Chunks)))
glog.V(3).Infof("GetObject: Set PartsCount=%d for multipart GET with PartNumber=%d", len(objectEntryForSSE.Chunks), partNumber)
// Get the specific part chunk
chunkIndex := partNumber - 1
partChunk := objectEntryForSSE.Chunks[chunkIndex]
// Override ETag with the specific part's ETag
if partChunk.ETag != "" {
// chunk.ETag is base64-encoded, convert to hex for S3 compatibility
if md5Bytes, decodeErr := base64.StdEncoding.DecodeString(partChunk.ETag); decodeErr == nil {
partETag := fmt.Sprintf("%x", md5Bytes)
w.Header().Set("ETag", "\""+partETag+"\"")
glog.V(3).Infof("GetObject: Override ETag with part %d ETag: %s", partNumber, partETag)
}
}
// CRITICAL: Set Range header to read only this part's bytes (matches filer logic)
// This ensures we stream only the specific part, not the entire object
rangeHeader := fmt.Sprintf("bytes=%d-%d", partChunk.Offset, uint64(partChunk.Offset)+partChunk.Size-1)
r.Header.Set("Range", rangeHeader)
glog.V(3).Infof("GetObject: Set Range header for part %d: %s", partNumber, rangeHeader)
}
}
// NEW OPTIMIZATION: Stream directly from volume servers, bypassing filer proxy // NEW OPTIMIZATION: Stream directly from volume servers, bypassing filer proxy
// This eliminates the 19ms filer proxy overhead // This eliminates the 19ms filer proxy overhead
// SSE decryption is handled inline during streaming // SSE decryption is handled inline during streaming
@ -1178,50 +1221,39 @@ func (s3a *S3ApiServer) HeadObjectHandler(w http.ResponseWriter, r *http.Request
s3a.setResponseHeaders(w, objectEntryForSSE, totalSize) s3a.setResponseHeaders(w, objectEntryForSSE, totalSize)
// Check if PartNumber query parameter is present (for multipart objects) // Check if PartNumber query parameter is present (for multipart objects)
// Try both "partNumber" (S3 API standard) and "PartNumber" (some clients)
glog.V(3).Infof("HeadObject: Full query string: %q, All params: %v", r.URL.RawQuery, r.URL.Query())
// This logic matches the filer handler for consistency
partNumberStr := r.URL.Query().Get("partNumber") partNumberStr := r.URL.Query().Get("partNumber")
if partNumberStr == "" { if partNumberStr == "" {
partNumberStr = r.URL.Query().Get("PartNumber") partNumberStr = r.URL.Query().Get("PartNumber")
} }
glog.V(3).Infof("HeadObject: partNumberStr=%q, Extended!=nil=%v, chunks=%d",
partNumberStr, objectEntryForSSE.Extended != nil, len(objectEntryForSSE.Chunks))
if partNumberStr != "" && objectEntryForSSE.Extended != nil {
// If this is a multipart object, add the parts count header
if partsCountStr, exists := objectEntryForSSE.Extended[s3_constants.SeaweedFSMultipartPartsCount]; exists {
w.Header().Set(s3_constants.AmzMpPartsCount, string(partsCountStr))
glog.V(3).Infof("HeadObject: Set PartsCount=%s for multipart object", string(partsCountStr))
// If PartNumber is specified, set headers (matching filer logic)
if partNumberStr != "" {
if partNumber, parseErr := strconv.Atoi(partNumberStr); parseErr == nil && partNumber > 0 {
// Validate part number (1-based)
if partNumber > len(objectEntryForSSE.Chunks) {
glog.Warningf("HeadObject: Invalid part number %d, object has %d chunks", partNumber, len(objectEntryForSSE.Chunks))
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidPart)
return
} }
// Override ETag with the specific part's ETag
if partNumber, parseErr := strconv.Atoi(partNumberStr); parseErr == nil && partNumber > 0 {
// Part numbers are 1-based, chunks are 0-based
chunkIndex := partNumber - 1
glog.V(3).Infof("HeadObject: partNumber=%d, chunkIndex=%d, numChunks=%d",
partNumber, chunkIndex, len(objectEntryForSSE.Chunks))
// Set parts count header (use actual chunk count like filer does)
w.Header().Set(s3_constants.AmzMpPartsCount, strconv.Itoa(len(objectEntryForSSE.Chunks)))
glog.V(3).Infof("HeadObject: Set PartsCount=%d for part %d", len(objectEntryForSSE.Chunks), partNumber)
if chunkIndex < len(objectEntryForSSE.Chunks) {
chunk := objectEntryForSSE.Chunks[chunkIndex]
glog.V(3).Infof("HeadObject: chunk[%d].ETag=%q", chunkIndex, chunk.ETag)
// Get the specific part chunk
chunkIndex := partNumber - 1
partChunk := objectEntryForSSE.Chunks[chunkIndex]
if chunk.ETag != "" {
// Override ETag with the specific part's ETag
if partChunk.ETag != "" {
// chunk.ETag is base64-encoded, convert to hex for S3 compatibility // chunk.ETag is base64-encoded, convert to hex for S3 compatibility
if md5Bytes, decodeErr := base64.StdEncoding.DecodeString(chunk.ETag); decodeErr == nil {
if md5Bytes, decodeErr := base64.StdEncoding.DecodeString(partChunk.ETag); decodeErr == nil {
partETag := fmt.Sprintf("%x", md5Bytes) partETag := fmt.Sprintf("%x", md5Bytes)
w.Header().Set("ETag", "\""+partETag+"\"") w.Header().Set("ETag", "\""+partETag+"\"")
glog.V(3).Infof("HeadObject: Override ETag with part %d ETag: %s", partNumber, partETag) glog.V(3).Infof("HeadObject: Override ETag with part %d ETag: %s", partNumber, partETag)
} else {
glog.Warningf("HeadObject: Failed to decode chunk ETag: %v", decodeErr)
} }
} else {
glog.Warningf("HeadObject: chunk[%d].ETag is empty", chunkIndex)
}
} else {
glog.Warningf("HeadObject: chunkIndex %d out of range (have %d chunks)", chunkIndex, len(objectEntryForSSE.Chunks))
} }
} else {
glog.Warningf("HeadObject: Failed to parse partNumber=%q: %v", partNumberStr, parseErr)
} }
} }

Loading…
Cancel
Save