mirror of
https://github.com/versity/versitygw.git
synced 2026-09-19 14:34:19 +00:00
feat: implements conditional reads for GetObject and HeadObject
Closes #882 Implements conditional reads for `GetObject` and `HeadObject` in the gateway for both POSIX and Azure backends. The behavior is controlled by the `If-Match`, `If-None-Match`, `If-Modified-Since`, and `If-Unmodified-Since` request headers, where the first two perform ETag comparisons and the latter two compare against the object’s `LastModified` date. No validation is performed for invalid ETags or malformed date formats, and precondition date headers are expected to follow RFC1123; otherwise, they are ignored. The Integration tests cover all possible combinations of conditional headers, ensuring the feature is 100% AWS S3–compatible.
This commit is contained in:
@@ -450,12 +450,18 @@ func (c S3ApiController) GetObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
}, s3err.GetInvalidChecksumHeaderErr("x-amz-checksum-mode")
|
||||
}
|
||||
|
||||
conditionalHeaders := utils.ParsePreconditionHeaders(ctx)
|
||||
|
||||
res, err := c.be.GetObject(ctx.Context(), &s3.GetObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
Range: &acceptRange,
|
||||
VersionId: &versionId,
|
||||
ChecksumMode: checksumMode,
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
Range: &acceptRange,
|
||||
IfMatch: conditionalHeaders.IfMatch,
|
||||
IfNoneMatch: conditionalHeaders.IfNoneMatch,
|
||||
IfModifiedSince: conditionalHeaders.IfModSince,
|
||||
IfUnmodifiedSince: conditionalHeaders.IfUnmodeSince,
|
||||
VersionId: &versionId,
|
||||
ChecksumMode: checksumMode,
|
||||
})
|
||||
if err != nil {
|
||||
var headers map[string]*string
|
||||
|
||||
@@ -90,14 +90,20 @@ func (c S3ApiController) HeadObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
}, s3err.GetInvalidChecksumHeaderErr("x-amz-checksum-mode")
|
||||
}
|
||||
|
||||
conditionalHeaders := utils.ParsePreconditionHeaders(ctx)
|
||||
|
||||
res, err := c.be.HeadObject(ctx.Context(),
|
||||
&s3.HeadObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
PartNumber: partNumber,
|
||||
VersionId: &versionId,
|
||||
ChecksumMode: checksumMode,
|
||||
Range: &objRange,
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
PartNumber: partNumber,
|
||||
VersionId: &versionId,
|
||||
ChecksumMode: checksumMode,
|
||||
Range: &objRange,
|
||||
IfMatch: conditionalHeaders.IfMatch,
|
||||
IfNoneMatch: conditionalHeaders.IfNoneMatch,
|
||||
IfModifiedSince: conditionalHeaders.IfModSince,
|
||||
IfUnmodifiedSince: conditionalHeaders.IfUnmodeSince,
|
||||
})
|
||||
if err != nil {
|
||||
var headers map[string]*string
|
||||
|
||||
@@ -642,6 +642,59 @@ func ParseCreateMpChecksumHeaders(ctx *fiber.Ctx) (types.ChecksumAlgorithm, type
|
||||
return algo, chType, nil
|
||||
}
|
||||
|
||||
// ConditionalHeaders holds the conditional header values
|
||||
type ConditionalHeaders struct {
|
||||
IfMatch *string
|
||||
IfNoneMatch *string
|
||||
IfModSince *time.Time
|
||||
IfUnmodeSince *time.Time
|
||||
}
|
||||
|
||||
// ParsePreconditionHeaders parses the precondition headers:
|
||||
// - If-Match
|
||||
// - If-None-Match
|
||||
// - If-Modified-Since
|
||||
// - If-Unmodified-Since
|
||||
func ParsePreconditionHeaders(ctx *fiber.Ctx) ConditionalHeaders {
|
||||
ifMatch, ifNoneMatch := ParsePreconditionMatchHeaders(ctx)
|
||||
ifModSince, ifUnmodeSince := ParsePreconditionDateHeaders(ctx)
|
||||
|
||||
return ConditionalHeaders{
|
||||
IfMatch: ifMatch,
|
||||
IfNoneMatch: ifNoneMatch,
|
||||
IfModSince: ifModSince,
|
||||
IfUnmodeSince: ifUnmodeSince,
|
||||
}
|
||||
}
|
||||
|
||||
// ParsePreconditionMatchHeaders extracts "If-Match" and "If-None-Match" headers from fiber Ctx
|
||||
func ParsePreconditionMatchHeaders(ctx *fiber.Ctx) (*string, *string) {
|
||||
return GetStringPtr(ctx.Get("If-Match")), GetStringPtr(ctx.Get("If-None-Match"))
|
||||
}
|
||||
|
||||
// ParsePreconditionDateHeaders parses the "If-Modified-Since" and "If-Unmodified-Since"
|
||||
// headers from fiber context to *time.Time
|
||||
func ParsePreconditionDateHeaders(ctx *fiber.Ctx) (*time.Time, *time.Time) {
|
||||
ifModSince := ctx.Get("If-Modified-Since")
|
||||
ifUnmodSince := ctx.Get("If-Unmodified-Since")
|
||||
|
||||
var ifModSinceParsed, ifUnmodSinceParsed *time.Time
|
||||
|
||||
// the time format should be a valid RFC1123
|
||||
// if parsing fails, ignore the error and leave the value as nil
|
||||
modParsed, err := time.Parse(time.RFC1123, ifModSince)
|
||||
if err == nil {
|
||||
ifModSinceParsed = &modParsed
|
||||
}
|
||||
|
||||
unmodParsed, err := time.Parse(time.RFC1123, ifUnmodSince)
|
||||
if err == nil {
|
||||
ifUnmodSinceParsed = &unmodParsed
|
||||
}
|
||||
|
||||
return ifModSinceParsed, ifUnmodSinceParsed
|
||||
}
|
||||
|
||||
// TagLimit specifies the allowed tag count in a tag set
|
||||
type TagLimit int
|
||||
|
||||
|
||||
Reference in New Issue
Block a user