|
|
@ -263,28 +263,18 @@ func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request) |
|
|
// Check for specific version ID in query parameters
|
|
|
// Check for specific version ID in query parameters
|
|
|
versionId := r.URL.Query().Get("versionId") |
|
|
versionId := r.URL.Query().Get("versionId") |
|
|
|
|
|
|
|
|
// Only check versioning configuration if client requests it or if we might need it
|
|
|
|
|
|
// This avoids unnecessary bucket config lookups for common non-versioned read requests
|
|
|
|
|
|
var versioningConfigured bool |
|
|
|
|
|
var err error |
|
|
|
|
|
|
|
|
|
|
|
// Fast path: skip versioning check if no versionId parameter (most common case)
|
|
|
|
|
|
if versionId != "" { |
|
|
|
|
|
versioningConfigured, err = s3a.isVersioningConfigured(bucket) |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
if err == filer_pb.ErrNotFound { |
|
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
glog.Errorf("Error checking versioning status for bucket %s: %v", bucket, err) |
|
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
if !versioningConfigured { |
|
|
|
|
|
// Client requested a version but versioning not enabled - return NoSuchKey per AWS S3 behavior
|
|
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey) |
|
|
|
|
|
|
|
|
// Check if versioning is configured for the bucket (Enabled or Suspended)
|
|
|
|
|
|
// Note: We need to check this even if versionId is empty, because versioned buckets
|
|
|
|
|
|
// handle even "get latest version" requests differently (through .versions directory)
|
|
|
|
|
|
versioningConfigured, err := s3a.isVersioningConfigured(bucket) |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
if err == filer_pb.ErrNotFound { |
|
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket) |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
glog.Errorf("Error checking versioning status for bucket %s: %v", bucket, err) |
|
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
|
|
|
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
glog.V(1).Infof("GetObject: bucket %s, object %s, versioningConfigured=%v, versionId=%s", bucket, object, versioningConfigured, versionId) |
|
|
glog.V(1).Infof("GetObject: bucket %s, object %s, versioningConfigured=%v, versionId=%s", bucket, object, versioningConfigured, versionId) |
|
|
@ -366,20 +356,11 @@ func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request) |
|
|
// For versioned objects, reuse the already-fetched entry
|
|
|
// For versioned objects, reuse the already-fetched entry
|
|
|
objectEntryForSSE = entry |
|
|
objectEntryForSSE = entry |
|
|
} else { |
|
|
} else { |
|
|
// For non-versioned objects, fetch entry once and use for both SSE and Range checks
|
|
|
|
|
|
|
|
|
// For non-versioned objects, fetch entry once for SSE processing
|
|
|
objectPath := fmt.Sprintf("%s/%s%s", s3a.option.BucketsPath, bucket, object) |
|
|
objectPath := fmt.Sprintf("%s/%s%s", s3a.option.BucketsPath, bucket, object) |
|
|
fetchedEntry, fetchErr := s3a.getEntry("", objectPath) |
|
|
fetchedEntry, fetchErr := s3a.getEntry("", objectPath) |
|
|
if fetchErr == nil { |
|
|
if fetchErr == nil { |
|
|
objectEntryForSSE = fetchedEntry |
|
|
objectEntryForSSE = fetchedEntry |
|
|
// Check if this is an SSE object for Range request handling
|
|
|
|
|
|
if originalRangeHeader != "" { |
|
|
|
|
|
primarySSEType := s3a.detectPrimarySSEType(fetchedEntry) |
|
|
|
|
|
if primarySSEType == s3_constants.SSETypeC || primarySSEType == s3_constants.SSETypeKMS { |
|
|
|
|
|
sseObject = true |
|
|
|
|
|
// Temporarily remove Range header to get full encrypted data from filer
|
|
|
|
|
|
r.Header.Del("Range") |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} else if !errors.Is(fetchErr, filer_pb.ErrNotFound) { |
|
|
} else if !errors.Is(fetchErr, filer_pb.ErrNotFound) { |
|
|
glog.Errorf("GetObjectHandler: failed to get entry for SSE check: %v", fetchErr) |
|
|
glog.Errorf("GetObjectHandler: failed to get entry for SSE check: %v", fetchErr) |
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
|
|
@ -387,6 +368,17 @@ func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Check if this is an SSE object for Range request handling
|
|
|
|
|
|
// This applies to both versioned and non-versioned objects
|
|
|
|
|
|
if originalRangeHeader != "" && objectEntryForSSE != nil { |
|
|
|
|
|
primarySSEType := s3a.detectPrimarySSEType(objectEntryForSSE) |
|
|
|
|
|
if primarySSEType == s3_constants.SSETypeC || primarySSEType == s3_constants.SSETypeKMS { |
|
|
|
|
|
sseObject = true |
|
|
|
|
|
// Temporarily remove Range header to get full encrypted data from filer
|
|
|
|
|
|
r.Header.Del("Range") |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
s3a.proxyToFiler(w, r, destUrl, false, func(proxyResponse *http.Response, w http.ResponseWriter) (statusCode int, bytesTransferred int64) { |
|
|
s3a.proxyToFiler(w, r, destUrl, false, func(proxyResponse *http.Response, w http.ResponseWriter) (statusCode int, bytesTransferred int64) { |
|
|
// Restore the original Range header for SSE processing
|
|
|
// Restore the original Range header for SSE processing
|
|
|
if sseObject && originalRangeHeader != "" { |
|
|
if sseObject && originalRangeHeader != "" { |
|
|
@ -430,28 +422,18 @@ func (s3a *S3ApiServer) HeadObjectHandler(w http.ResponseWriter, r *http.Request |
|
|
// Check for specific version ID in query parameters
|
|
|
// Check for specific version ID in query parameters
|
|
|
versionId := r.URL.Query().Get("versionId") |
|
|
versionId := r.URL.Query().Get("versionId") |
|
|
|
|
|
|
|
|
// Only check versioning configuration if client requests it or if we might need it
|
|
|
|
|
|
// This avoids unnecessary bucket config lookups for common non-versioned read requests
|
|
|
|
|
|
var versioningConfigured bool |
|
|
|
|
|
var err error |
|
|
|
|
|
|
|
|
|
|
|
// Fast path: skip versioning check if no versionId parameter (most common case)
|
|
|
|
|
|
if versionId != "" { |
|
|
|
|
|
versioningConfigured, err = s3a.isVersioningConfigured(bucket) |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
if err == filer_pb.ErrNotFound { |
|
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
glog.Errorf("Error checking versioning status for bucket %s: %v", bucket, err) |
|
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
if !versioningConfigured { |
|
|
|
|
|
// Client requested a version but versioning not enabled - return NoSuchKey per AWS S3 behavior
|
|
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey) |
|
|
|
|
|
|
|
|
// Check if versioning is configured for the bucket (Enabled or Suspended)
|
|
|
|
|
|
// Note: We need to check this even if versionId is empty, because versioned buckets
|
|
|
|
|
|
// handle even "get latest version" requests differently (through .versions directory)
|
|
|
|
|
|
versioningConfigured, err := s3a.isVersioningConfigured(bucket) |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
if err == filer_pb.ErrNotFound { |
|
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket) |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
glog.Errorf("Error checking versioning status for bucket %s: %v", bucket, err) |
|
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
|
|
|
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var destUrl string |
|
|
var destUrl string |
|
|
|