This is an automated email from the ASF dual-hosted git repository. rob pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-trafficcontrol.git
commit 4449a94fa84609a6d0949f3ffb6652686cd121c3 Author: Jan van Doorn <jan_vando...@comcast.com> AuthorDate: Wed May 16 08:58:06 2018 -0600 Add rfc package, move stuff around to accomodate --- grove/cache/handler.go | 3 +- grove/cache/retryinggetter.go | 5 +- grove/integration_test/compare_gets.go | 140 +++++++++++++++++++++++++++ grove/plugin/http_cacheinspector.go | 3 +- grove/remap/remap.go | 7 +- grove/{remap => rfc}/rules.go | 166 +++++++++++++++++++++++++++++++-- grove/{remap => rfc}/rules_test.go | 6 +- grove/web/util.go | 145 +--------------------------- 8 files changed, 314 insertions(+), 161 deletions(-) diff --git a/grove/cache/handler.go b/grove/cache/handler.go index a7a7f4a..216378c 100644 --- a/grove/cache/handler.go +++ b/grove/cache/handler.go @@ -27,6 +27,7 @@ import ( "github.com/apache/incubator-trafficcontrol/grove/remap" "github.com/apache/incubator-trafficcontrol/grove/remapdata" + "github.com/apache/incubator-trafficcontrol/grove/rfc" "github.com/apache/incubator-trafficcontrol/grove/stat" "github.com/apache/incubator-trafficcontrol/grove/thread" "github.com/apache/incubator-trafficcontrol/grove/web" @@ -261,7 +262,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } reqHeaders := r.Header - canReuseStored := remap.CanReuseStored(reqHeaders, cacheObj.RespHeaders, reqCacheControl, cacheObj.RespCacheControl, cacheObj.ReqHeaders, cacheObj.ReqRespTime, cacheObj.RespRespTime, h.strictRFC) + canReuseStored := rfc.CanReuseStored(reqHeaders, cacheObj.RespHeaders, reqCacheControl, cacheObj.RespCacheControl, cacheObj.ReqHeaders, cacheObj.ReqRespTime, cacheObj.RespRespTime, h.strictRFC) if canReuseStored != remapdata.ReuseCan { // run the BeforeParentRequest hook for revalidations / ReuseCannot beforeParentRequestData := plugin.BeforeParentRequestData{Req: r, RemapRule: remappingProducer.Name()} diff --git a/grove/cache/retryinggetter.go b/grove/cache/retryinggetter.go index ee60017..b70a6d4 100644 --- a/grove/cache/retryinggetter.go +++ b/grove/cache/retryinggetter.go @@ -23,6 +23,7 @@ import ( "github.com/apache/incubator-trafficcontrol/grove/cacheobj" "github.com/apache/incubator-trafficcontrol/grove/icache" "github.com/apache/incubator-trafficcontrol/grove/remap" + "github.com/apache/incubator-trafficcontrol/grove/rfc" "github.com/apache/incubator-trafficcontrol/grove/thread" "github.com/apache/incubator-trafficcontrol/grove/web" @@ -56,7 +57,7 @@ func (r *Retrier) Get(req *http.Request, obj *cacheobj.CacheObj) (*cacheobj.Cach retryGetFunc := func(remapping remap.Remapping, retryFailures bool, obj *cacheobj.CacheObj) *cacheobj.CacheObj { // return true for Revalidate, and issue revalidate requests separately. canReuse := func(cacheObj *cacheobj.CacheObj) bool { - return remap.CanReuse(r.ReqHdr, r.ReqCacheControl, cacheObj, r.H.strictRFC, true) + return rfc.CanReuse(r.ReqHdr, r.ReqCacheControl, cacheObj, r.H.strictRFC, true) } getAndCache := func() *cacheobj.CacheObj { return GetAndCache(remapping.Request, remapping.ProxyURL, remapping.CacheKey, remapping.Name, remapping.Request.Header, r.ReqTime, r.H.strictRFC, remapping.Cache, r.H.ruleThrottlers[remapping.Name], obj, remapping.Timeout, retryFailures, remapping.RetryNum, remapping.RetryCodes, remapping.Transport, r.ReqID) @@ -166,7 +167,7 @@ func GetAndCache( if revalidateObj == nil || respCode != http.StatusNotModified { log.Debugf("GetAndCache new %v (reqid %v)\n", cacheKey, reqID) obj = cacheobj.New(reqHeader, respBody, respCode, respCode, proxyURLStr, respHeader, reqTime, reqRespTime, respRespTime, lastModified) - if !remap.CanCache(req.Method, reqHeader, respCode, respHeader, strictRFC) { + if !rfc.CanCache(req.Method, reqHeader, respCode, respHeader, strictRFC) { return obj // return without caching } } else { diff --git a/grove/integration_test/compare_gets.go b/grove/integration_test/compare_gets.go new file mode 100644 index 0000000..08d3aca --- /dev/null +++ b/grove/integration_test/compare_gets.go @@ -0,0 +1,140 @@ +package main + +import ( + "flag" + "fmt" + "github.com/apache/incubator-trafficcontrol/grove/web" + "io/ioutil" + "log" + "net/http" + "os" + "strings" +) + +type responseType struct { + Headers http.Header + Body []byte +} + +func httpGet(URL, headers string) responseType { + client := &http.Client{} + req, err := http.NewRequest("GET", URL, nil) + if err != nil { + fmt.Println("ERROR in httpGet") + } + //log.Printf(">>>%v<<< %v\n", headers, len(strings.Split(headers, "."))) + for _, hdrString := range strings.Split(headers, ",") { + //log.Println(">>> ", hdrString) + if hdrString == "" { + continue + } + parts := strings.Split(hdrString, ":") + if parts[0] == "Host" { + req.Host = parts[1] + } else { + //log.Println("> ", parts) + req.Header.Set(parts[0], parts[1]) + } + } + //log.Printf(">>>> %v", req) + resp, err := client.Do(req) + if err != nil { + fmt.Println("ERROR in httpGet") + } + defer resp.Body.Close() + var response responseType + response.Headers = web.CopyHeader(resp.Header) + response.Body, err = ioutil.ReadAll(resp.Body) + if err != nil { + fmt.Println("ERROR in httpGet (readall)") + } + return response +} + +func equal(a, b []byte) bool { + if a == nil || b == nil { + return false + } + + if a == nil && b == nil { + return true + } + if len(a) != len(b) { + return false + } + + for i := range a { + if a[i] != b[i] { + return false + } + } + + return true +} + +func equalStringSlices(a, b []string) bool { + if a == nil || b == nil { + return false + } + + if a == nil && b == nil { + return true + } + if len(a) != len(b) { + return false + } + + for i := range a { + if a[i] != b[i] { + return false + } + } + + return true +} + +func inStringSlice(str string, arr []string) bool { + for _, strEnt := range arr { + if strEnt == str { + return true + } + } + return false +} + +func compareResponses(response1 responseType, response2 responseType, ignoreHdrs []string) bool { + if !equal(response1.Body, response2.Body) { + return false + } + for hdrKey, _ := range response1.Headers { + if inStringSlice(hdrKey, ignoreHdrs) { + continue + } + if !equalStringSlices(response1.Headers[hdrKey], response2.Headers[hdrKey]) { + log.Printf("ERROR hdr %v doesn't match: \"%v\" != \"%v\"\n", hdrKey, response1.Headers[hdrKey], response2.Headers[hdrKey]) + return false + } + //fmt.Printf(">>>>> %v\n", hdrKey) + } + + return true +} +func main() { + originURL := flag.String("org", "http://localhost", "The origin URL (default: \"http://localhost\")") + cacheURL := flag.String("cache", "http://localhost:8080", "The cache URL (default: \"http://localhost:8080\")") + path := flag.String("path", "", "The path to GET") + orgHdrs := flag.String("ohdrs", "", "Comma seperated list of headers to add to origin request") + cacheHdrs := flag.String("chdrs", "", "Comma separated list of headers to add to cache request") + ignoreHdrs := flag.String("ignorehdrs", "Server,Date", "Comma separated list of headers to ignore in the compare") + flag.Parse() + + resp := httpGet(*originURL+"/"+*path, *orgHdrs) + cresp := httpGet(*cacheURL+"/"+*path, *cacheHdrs) + if !compareResponses(resp, cresp, strings.Split(*ignoreHdrs, ",")) { + fmt.Println("FAIL: Body bytes don't match") + os.Exit(1) + + } + fmt.Println("PASS") + os.Exit(0) +} diff --git a/grove/plugin/http_cacheinspector.go b/grove/plugin/http_cacheinspector.go index 2cdb158..9409aee 100644 --- a/grove/plugin/http_cacheinspector.go +++ b/grove/plugin/http_cacheinspector.go @@ -26,6 +26,7 @@ import ( "github.com/apache/incubator-trafficcontrol/grove/web" "time" + "github.com/apache/incubator-trafficcontrol/grove/rfc" "github.com/apache/incubator-trafficcontrol/lib/go-log" ) @@ -178,7 +179,7 @@ func cacheinspect(icfg interface{}, d OnRequestData) bool { cacheObject, _ := d.Stats.CachePeek(key, cName) age := time.Now().Sub(cacheObject.ReqRespTime) - freshFor := web.FreshFor(cacheObject.RespHeaders, cacheObject.RespCacheControl, cacheObject.ReqRespTime, cacheObject.RespRespTime) + freshFor := rfc.FreshFor(cacheObject.RespHeaders, cacheObject.RespCacheControl, cacheObject.ReqRespTime, cacheObject.RespRespTime) w.Write([]byte(fmt.Sprintf(" %8d%8d%10s%22v%22v%12d <a href=\"http://%s%s?key=%s&cache=%s\">%s</a>\n", i, cacheObject.Code, bytefmt.ByteSize(cacheObject.Size), age, freshFor, cacheObject.HitCount, req.Host, CacheStatsEndpoint, url.QueryEscape(key), cName, key))) } diff --git a/grove/remap/remap.go b/grove/remap/remap.go index 2af529c..9793566 100644 --- a/grove/remap/remap.go +++ b/grove/remap/remap.go @@ -29,6 +29,7 @@ import ( "github.com/apache/incubator-trafficcontrol/grove/icache" "github.com/apache/incubator-trafficcontrol/grove/plugin" "github.com/apache/incubator-trafficcontrol/grove/remapdata" + "github.com/apache/incubator-trafficcontrol/grove/rfc" "github.com/apache/incubator-trafficcontrol/grove/web" "github.com/apache/incubator-trafficcontrol/lib/go-log" @@ -336,7 +337,7 @@ func LoadRemapRules(path string, pluginConfigLoaders map[string]plugin.LoadFunc, if remapRulesJSON.RetryCodes != nil { remapRules.RetryCodes = make(map[int]struct{}, len(*remapRulesJSON.RetryCodes)) for _, code := range *remapRulesJSON.RetryCodes { - if _, ok := ValidHTTPCodes[code]; !ok { + if _, ok := rfc.ValidHTTPCodes[code]; !ok { return nil, nil, nil, fmt.Errorf("error parsing rules: retry code invalid: %v", code) } remapRules.RetryCodes[code] = struct{}{} @@ -392,7 +393,7 @@ func LoadRemapRules(path string, pluginConfigLoaders map[string]plugin.LoadFunc, if jsonRule.RetryCodes != nil { rule.RetryCodes = make(map[int]struct{}, len(*jsonRule.RetryCodes)) for _, code := range *jsonRule.RetryCodes { - if _, ok := ValidHTTPCodes[code]; !ok { + if _, ok := rfc.ValidHTTPCodes[code]; !ok { return nil, nil, nil, fmt.Errorf("error parsing rule %v retry code invalid: %v", rule.Name, code) } rule.RetryCodes[code] = struct{}{} @@ -511,7 +512,7 @@ func makeTo(tosJSON []RemapRuleToJSON, rule remapdata.RemapRule, baseTransport * if toJSON.RetryCodes != nil { to.RetryCodes = make(map[int]struct{}, len(*toJSON.RetryCodes)) for _, code := range *toJSON.RetryCodes { - if _, ok := ValidHTTPCodes[code]; !ok { + if _, ok := rfc.ValidHTTPCodes[code]; !ok { return nil, fmt.Errorf("error parsing to %v retry code invalid: %v", to.URL, code) } to.RetryCodes[code] = struct{}{} diff --git a/grove/remap/rules.go b/grove/rfc/rules.go similarity index 68% rename from grove/remap/rules.go rename to grove/rfc/rules.go index 31ba715..1879bc7 100644 --- a/grove/remap/rules.go +++ b/grove/rfc/rules.go @@ -1,4 +1,4 @@ -package remap +package rfc /* Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +14,10 @@ package remap limitations under the License. */ +// Package rfc contains functions implementing RFC 7234, 2616, and other RFCs. +// When changing functions, be sure they still conform to the corresponding RFC. +// When adding symbols, document the RFC and section they correspond to. + import ( "net/http" "strings" @@ -24,6 +28,8 @@ import ( "github.com/apache/incubator-trafficcontrol/grove/web" "github.com/apache/incubator-trafficcontrol/lib/go-log" + "math" + "strconv" ) // ValidHTTPCodes provides fast lookup whether a HTTP response code is valid per RFC7234§3 @@ -266,8 +272,8 @@ func fresh( respReqTime time.Time, respRespTime time.Time, ) bool { - freshnessLifetime := web.GetFreshnessLifetime(respHeaders, respCacheControl) - currentAge := web.GetCurrentAge(respHeaders, respReqTime, respRespTime) + freshnessLifetime := getFreshnessLifetime(respHeaders, respCacheControl) + currentAge := getCurrentAge(respHeaders, respReqTime, respRespTime) log.Debugf("Fresh: freshnesslifetime %v currentAge %v\n", freshnessLifetime, currentAge) fresh := freshnessLifetime > currentAge return fresh @@ -275,12 +281,12 @@ func fresh( // inMinFresh returns whether the given response is within the `min-fresh` request directive. If no `min-fresh` directive exists in the request, `true` is returned. func inMinFresh(respHeaders http.Header, reqCacheControl web.CacheControl, respCacheControl web.CacheControl, respReqTime time.Time, respRespTime time.Time) bool { - minFresh, ok := web.GetHTTPDeltaSecondsCacheControl(reqCacheControl, "min-fresh") + minFresh, ok := getHTTPDeltaSecondsCacheControl(reqCacheControl, "min-fresh") if !ok { return true // no min-fresh => within min-fresh } - freshnessLifetime := web.GetFreshnessLifetime(respHeaders, respCacheControl) - currentAge := web.GetCurrentAge(respHeaders, respReqTime, respRespTime) + freshnessLifetime := getFreshnessLifetime(respHeaders, respCacheControl) + currentAge := getCurrentAge(respHeaders, respReqTime, respRespTime) inMinFresh := minFresh < (freshnessLifetime - currentAge) log.Debugf("inMinFresh minFresh %v freshnessLifetime %v currentAge %v => %v < (%v - %v) = %v\n", minFresh, freshnessLifetime, currentAge, minFresh, freshnessLifetime, currentAge, inMinFresh) return inMinFresh @@ -322,13 +328,13 @@ func allowedStale(respHeaders http.Header, reqCacheControl web.CacheControl, res // InMaxStale returns whether the given response is within the `max-stale` request directive. If no `max-stale` directive exists in the request, `true` is returned. func inMaxStale(respHeaders http.Header, respCacheControl web.CacheControl, respReqTime time.Time, respRespTime time.Time) bool { - maxStale, ok := web.GetHTTPDeltaSecondsCacheControl(respCacheControl, "max-stale") + maxStale, ok := getHTTPDeltaSecondsCacheControl(respCacheControl, "max-stale") if !ok { // maxStale = 5 // debug return true // no max-stale => within max-stale } - freshnessLifetime := web.GetFreshnessLifetime(respHeaders, respCacheControl) - currentAge := web.GetCurrentAge(respHeaders, respReqTime, respRespTime) + freshnessLifetime := getFreshnessLifetime(respHeaders, respCacheControl) + currentAge := getCurrentAge(respHeaders, respReqTime, respRespTime) log.Errorf("DEBUGR InMaxStale maxStale %v freshnessLifetime %v currentAge %v => %v > (%v, %v)\n", maxStale, freshnessLifetime, currentAge, maxStale, currentAge, freshnessLifetime) // DEBUG inMaxStale := maxStale > (currentAge - freshnessLifetime) return inMaxStale @@ -378,3 +384,145 @@ func hasPragmaNoCache(reqHeaders http.Header) bool { } return false } + +// GetHTTPDeltaSeconds is a helper function which gets an HTTP Delta Seconds from the given map (which is typically a `http.Header` or `CacheControl`. Returns false if the given key doesn't exist in the map, or if the value isn't a valid Delta Seconds per RFC2616§3.3.2. +func getHTTPDeltaSeconds(m map[string][]string, key string) (time.Duration, bool) { + maybeSeconds, ok := m[key] + if !ok { + return 0, false + } + if len(maybeSeconds) == 0 { + return 0, false + } + maybeSec := maybeSeconds[0] + + seconds, err := strconv.ParseUint(maybeSec, 10, 64) + if err != nil { + return 0, false + } + return time.Duration(seconds) * time.Second, true +} + +// getHTTPDeltaSeconds is a helper function which gets an HTTP Delta Seconds from the given map (which is typically a `http.Header` or `CacheControl`. Returns false if the given key doesn't exist in the map, or if the value isn't a valid Delta Seconds per RFC2616§3.3.2. +func getHTTPDeltaSecondsCacheControl(m map[string]string, key string) (time.Duration, bool) { + maybeSec, ok := m[key] + if !ok { + return 0, false + } + seconds, err := strconv.ParseUint(maybeSec, 10, 64) + if err != nil { + return 0, false + } + return time.Duration(seconds) * time.Second, true +} + +// getFreshnessLifetime calculates the freshness_lifetime per RFC7234§4.2.1 +func getFreshnessLifetime(respHeaders http.Header, respCacheControl web.CacheControl) time.Duration { + if s, ok := getHTTPDeltaSecondsCacheControl(respCacheControl, "s-maxage"); ok { + return s + } + if s, ok := getHTTPDeltaSecondsCacheControl(respCacheControl, "max-age"); ok { + return s + } + + getExpires := func() (time.Duration, bool) { + expires, ok := web.GetHTTPDate(respHeaders, "Expires") + if !ok { + return 0, false + } + date, ok := web.GetHTTPDate(respHeaders, "Date") + if !ok { + return 0, false + } + return expires.Sub(date), true + } + if s, ok := getExpires(); ok { + return s + } + return heuristicFreshness(respHeaders) +} + +const Day = time.Hour * time.Duration(24) + +// HeuristicFreshness follows the recommendation of RFC7234§4.2.2 and returns the min of 10% of the (Date - Last-Modified) headers and 24 hours, if they exist, and 24 hours if they don't. +// TODO: smarter and configurable heuristics +func heuristicFreshness(respHeaders http.Header) time.Duration { + sinceLastModified, ok := sinceLastModified(respHeaders) + if !ok { + return Day + } + freshness := time.Duration(math.Min(float64(Day), float64(sinceLastModified))) + return freshness +} + +func sinceLastModified(headers http.Header) (time.Duration, bool) { + lastModified, ok := web.GetHTTPDate(headers, "last-modified") + if !ok { + return 0, false + } + date, ok := web.GetHTTPDate(headers, "date") + if !ok { + return 0, false + } + return date.Sub(lastModified), true +} + +// ageValue is used to calculate current_age per RFC7234§4.2.3 +func ageValue(respHeaders http.Header) time.Duration { + s, ok := getHTTPDeltaSeconds(respHeaders, "age") + if !ok { + return 0 + } + return s +} + +// dateValue is used to calculate current_age per RFC7234§4.2.3. It returns time, or false if the response had no Date header (in violation of HTTP/1.1). +func dateValue(respHeaders http.Header) (time.Time, bool) { + return web.GetHTTPDate(respHeaders, "date") +} + +func apparentAge(respHeaders http.Header, respRespTime time.Time) time.Duration { + dateValue, ok := dateValue(respHeaders) + if !ok { + return 0 // TODO log warning? + } + rawAge := respRespTime.Sub(dateValue) + return time.Duration(math.Max(0.0, float64(rawAge))) +} + +func responseDelay(respReqTime time.Time, respRespTime time.Time) time.Duration { + return respRespTime.Sub(respReqTime) +} + +func correctedAgeValue(respHeaders http.Header, respReqTime time.Time, respRespTime time.Time) time.Duration { + return ageValue(respHeaders) + responseDelay(respReqTime, respRespTime) +} + +func correctedInitialAge(respHeaders http.Header, respReqTime time.Time, respRespTime time.Time) time.Duration { + return time.Duration(math.Max(float64(apparentAge(respHeaders, respRespTime)), float64(correctedAgeValue(respHeaders, respReqTime, respRespTime)))) +} + +func residentTime(respRespTime time.Time) time.Duration { + return time.Now().Sub(respRespTime) +} + +func getCurrentAge(respHeaders http.Header, respReqTime time.Time, respRespTime time.Time) time.Duration { + correctedInitial := correctedInitialAge(respHeaders, respReqTime, respRespTime) + resident := residentTime(respRespTime) + log.Debugf("getCurrentAge: correctedInitialAge %v residentTime %v\n", correctedInitial, resident) + return correctedInitial + resident +} + +// FreshFor checks returns how long this object is still good for +func FreshFor( + respHeaders http.Header, + respCacheControl web.CacheControl, + respReqTime time.Time, + respRespTime time.Time, +) time.Duration { + freshnessLifetime := getFreshnessLifetime(respHeaders, respCacheControl) + currentAge := getCurrentAge(respHeaders, respReqTime, respRespTime) + log.Debugf("FreshFor: freshnesslifetime %v currentAge %v\n", freshnessLifetime, currentAge) + //fresh := freshnessLifetime > currentAge + return freshnessLifetime - currentAge +} diff --git a/grove/remap/rules_test.go b/grove/rfc/rules_test.go similarity index 99% rename from grove/remap/rules_test.go rename to grove/rfc/rules_test.go index 91bdde9..f364aad 100644 --- a/grove/remap/rules_test.go +++ b/grove/rfc/rules_test.go @@ -1,4 +1,4 @@ -package remap +package rfc /* Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +14,10 @@ package remap limitations under the License. */ +// Package rfc contains functions implementing RFC 7234, 2616, and other RFCs. +// When changing functions, be sure they still conform to the corresponding RFC. +// When adding symbols, document the RFC and section they correspond to. + import ( "net/http" "os" diff --git a/grove/web/util.go b/grove/web/util.go index 23c7922..ff13e44 100644 --- a/grove/web/util.go +++ b/grove/web/util.go @@ -22,9 +22,8 @@ import ( "strings" "time" + //"github.com/apache/incubator-trafficcontrol/grove/rfc" "github.com/apache/incubator-trafficcontrol/lib/go-log" - "math" - "strconv" ) type Hdr struct { @@ -200,145 +199,3 @@ func ParseHTTPDate(d string) (time.Time, bool) { // RemapTextKey is the plugin shared data key inserted by grovetccfg for the Remap Line of the Delivery Service in Traffic Control, Traffic Ops. const RemapTextKey = "remap_text" - -const Day = time.Hour * time.Duration(24) - -// GetHTTPDeltaSeconds is a helper function which gets an HTTP Delta Seconds from the given map (which is typically a `http.Header` or `CacheControl`. Returns false if the given key doesn't exist in the map, or if the value isn't a valid Delta Seconds per RFC2616§3.3.2. -func GetHTTPDeltaSeconds(m map[string][]string, key string) (time.Duration, bool) { - maybeSeconds, ok := m[key] - if !ok { - return 0, false - } - if len(maybeSeconds) == 0 { - return 0, false - } - maybeSec := maybeSeconds[0] - - seconds, err := strconv.ParseUint(maybeSec, 10, 64) - if err != nil { - return 0, false - } - return time.Duration(seconds) * time.Second, true -} - -// GetHTTPDeltaSeconds is a helper function which gets an HTTP Delta Seconds from the given map (which is typically a `http.Header` or `CacheControl`. Returns false if the given key doesn't exist in the map, or if the value isn't a valid Delta Seconds per RFC2616§3.3.2. -func GetHTTPDeltaSecondsCacheControl(m map[string]string, key string) (time.Duration, bool) { - maybeSec, ok := m[key] - if !ok { - return 0, false - } - seconds, err := strconv.ParseUint(maybeSec, 10, 64) - if err != nil { - return 0, false - } - return time.Duration(seconds) * time.Second, true -} - -// HeuristicFreshness follows the recommendation of RFC7234§4.2.2 and returns the min of 10% of the (Date - Last-Modified) headers and 24 hours, if they exist, and 24 hours if they don't. -// TODO: smarter and configurable heuristics -func HeuristicFreshness(respHeaders http.Header) time.Duration { - sinceLastModified, ok := sinceLastModified(respHeaders) - if !ok { - return Day - } - freshness := time.Duration(math.Min(float64(Day), float64(sinceLastModified))) - return freshness -} - -func sinceLastModified(headers http.Header) (time.Duration, bool) { - lastModified, ok := GetHTTPDate(headers, "last-modified") - if !ok { - return 0, false - } - date, ok := GetHTTPDate(headers, "date") - if !ok { - return 0, false - } - return date.Sub(lastModified), true -} - -// GetFreshnessLifetime calculates the freshness_lifetime per RFC7234§4.2.1 -func GetFreshnessLifetime(respHeaders http.Header, respCacheControl CacheControl) time.Duration { - if s, ok := GetHTTPDeltaSecondsCacheControl(respCacheControl, "s-maxage"); ok { - return s - } - if s, ok := GetHTTPDeltaSecondsCacheControl(respCacheControl, "max-age"); ok { - return s - } - - getExpires := func() (time.Duration, bool) { - expires, ok := GetHTTPDate(respHeaders, "Expires") - if !ok { - return 0, false - } - date, ok := GetHTTPDate(respHeaders, "Date") - if !ok { - return 0, false - } - return expires.Sub(date), true - } - if s, ok := getExpires(); ok { - return s - } - return HeuristicFreshness(respHeaders) -} - -// t6AgeValue is used to calculate current_age per RFC7234§4.2.3 -func AgeValue(respHeaders http.Header) time.Duration { - s, ok := GetHTTPDeltaSeconds(respHeaders, "age") - if !ok { - return 0 - } - return s -} - -func GetCurrentAge(respHeaders http.Header, respReqTime time.Time, respRespTime time.Time) time.Duration { - correctedInitial := CorrectedInitialAge(respHeaders, respReqTime, respRespTime) - resident := residentTime(respRespTime) - log.Debugf("getCurrentAge: correctedInitialAge %v residentTime %v\n", correctedInitial, resident) - return correctedInitial + resident -} - -func CorrectedInitialAge(respHeaders http.Header, respReqTime time.Time, respRespTime time.Time) time.Duration { - return time.Duration(math.Max(float64(ApparentAge(respHeaders, respRespTime)), float64(CorrectedAgeValue(respHeaders, respReqTime, respRespTime)))) -} - -func CorrectedAgeValue(respHeaders http.Header, respReqTime time.Time, respRespTime time.Time) time.Duration { - return AgeValue(respHeaders) + responseDelay(respReqTime, respRespTime) -} - -func responseDelay(respReqTime time.Time, respRespTime time.Time) time.Duration { - return respRespTime.Sub(respReqTime) -} - -func residentTime(respRespTime time.Time) time.Duration { - return time.Now().Sub(respRespTime) -} - -func ApparentAge(respHeaders http.Header, respRespTime time.Time) time.Duration { - dateValue, ok := dateValue(respHeaders) - if !ok { - return 0 // TODO log warning? - } - rawAge := respRespTime.Sub(dateValue) - return time.Duration(math.Max(0.0, float64(rawAge))) -} - -// dateValue is used to calculate current_age per RFC7234§4.2.3. It returns time, or false if the response had no Date header (in violation of HTTP/1.1). -func dateValue(respHeaders http.Header) (time.Time, bool) { - return GetHTTPDate(respHeaders, "date") -} - -// FreshFor checks returns how long this object is still good for -func FreshFor( - respHeaders http.Header, - respCacheControl CacheControl, - respReqTime time.Time, - respRespTime time.Time, -) time.Duration { - freshnessLifetime := GetFreshnessLifetime(respHeaders, respCacheControl) - currentAge := GetCurrentAge(respHeaders, respReqTime, respRespTime) - log.Debugf("FreshFor: freshnesslifetime %v currentAge %v\n", freshnessLifetime, currentAge) - //fresh := freshnessLifetime > currentAge - return freshnessLifetime - currentAge -} -- To stop receiving notification emails like this one, please contact r...@apache.org.