diff --git a/weed/server/filer_server_handlers_read.go b/weed/server/filer_server_handlers_read.go index 56aee18be..8037b1d94 100644 --- a/weed/server/filer_server_handlers_read.go +++ b/weed/server/filer_server_handlers_read.go @@ -21,6 +21,64 @@ import ( "github.com/chrislusf/seaweedfs/weed/util" ) + +// Validates the preconditions. Returns true if GET/HEAD operation should not proceed. +// Preconditions supported are: +// If-Modified-Since +// If-Unmodified-Since +// If-Match +// If-None-Match +func checkPreconditions(w http.ResponseWriter, r *http.Request, entry *filer.Entry) bool { + + etag := filer.ETagEntry(entry) + /// When more than one conditional request header field is present in a + /// request, the order in which the fields are evaluated becomes + /// important. In practice, the fields defined in this document are + /// consistently implemented in a single, logical order, since "lost + /// update" preconditions have more strict requirements than cache + /// validation, a validated cache is more efficient than a partial + /// response, and entity tags are presumed to be more accurate than date + /// validators. https://tools.ietf.org/html/rfc7232#section-5 + if entry.Attr.Mtime.IsZero() { + return false + } + w.Header().Set("Last-Modified", entry.Attr.Mtime.UTC().Format(http.TimeFormat)) + + ifMatchETagHeader := r.Header.Get("If-Match") + ifUnmodifiedSinceHeader := r.Header.Get("If-Unmodified-Since") + if ifMatchETagHeader != "" { + if util.CanonicalizeETag(etag) != util.CanonicalizeETag(ifMatchETagHeader) { + w.WriteHeader(http.StatusPreconditionFailed) + return true + } + } else if ifUnmodifiedSinceHeader != "" { + if t, parseError := time.Parse(http.TimeFormat, ifUnmodifiedSinceHeader); parseError == nil { + if t.Before(entry.Attr.Mtime) { + w.WriteHeader(http.StatusPreconditionFailed) + return true + } + } + } + + ifNoneMatchETagHeader := r.Header.Get("If-None-Match") + ifModifiedSinceHeader := r.Header.Get("If-Modified-Since") + if ifNoneMatchETagHeader != "" { + if util.CanonicalizeETag(etag) == util.CanonicalizeETag(ifNoneMatchETagHeader) { + w.WriteHeader(http.StatusNotModified) + return true + } + } else if ifModifiedSinceHeader != "" { + if t, parseError := time.Parse(http.TimeFormat, ifModifiedSinceHeader); parseError == nil { + if t.After(entry.Attr.Mtime) { + w.WriteHeader(http.StatusNotModified) + return true + } + } + } + + return false +} + func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) { path := r.URL.Path @@ -61,10 +119,8 @@ func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) return } - // set etag etag := filer.ETagEntry(entry) - if ifm := r.Header.Get("If-Match"); ifm != "" && (ifm != "\""+etag+"\"" && ifm != etag) { - w.WriteHeader(http.StatusPreconditionFailed) + if checkPreconditions(w, r, entry) { return } @@ -81,19 +137,6 @@ func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) w.Header().Set("Content-Type", mimeType) } - // if modified since - if !entry.Attr.Mtime.IsZero() { - w.Header().Set("Last-Modified", entry.Attr.Mtime.UTC().Format(http.TimeFormat)) - if r.Header.Get("If-Modified-Since") != "" { - if t, parseError := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); parseError == nil { - if !t.Before(entry.Attr.Mtime) { - w.WriteHeader(http.StatusNotModified) - return - } - } - } - } - // print out the header from extended properties for k, v := range entry.Extended { if !strings.HasPrefix(k, "xattr-") { @@ -123,10 +166,6 @@ func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) w.Header().Set(xhttp.AmzTagCount, strconv.Itoa(tagCount)) } - if inm := r.Header.Get("If-None-Match"); inm == "\""+etag+"\"" { - w.WriteHeader(http.StatusNotModified) - return - } setEtag(w, etag) filename := entry.Name() diff --git a/weed/util/parse.go b/weed/util/parse.go index 0955db682..502f3a80f 100644 --- a/weed/util/parse.go +++ b/weed/util/parse.go @@ -61,3 +61,8 @@ func ParseHostPort(hostPort string) (filerServer string, filerPort int64, err er return } + +func CanonicalizeETag(etag string) string { + canonicalETag := strings.TrimPrefix(etag, "\"") + return strings.TrimSuffix(canonicalETag, "\"") +}