You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ro...@apache.org on 2018/05/23 16:34:09 UTC

[incubator-trafficcontrol] 03/04: Add rfc package, move stuff around to accomodate

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 <ja...@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
rob@apache.org.