// IsResponseCacheable returs whether the upstream server allows the requested // content to be saved in the cache. True result and 0 duration means that the // response has no expiry date. func IsResponseCacheable(code int, headers http.Header) bool { //!TODO: write a better custom implementation or fork the cacheobject - the API sucks //!TODO: correctly handle cache-control, pragma, etag and vary headers //!TODO: write unit tests if code != http.StatusOK && code != http.StatusPartialContent { return false } // For now, we do not cache encoded responses if headers.Get("Content-Encoding") != "" { return false } // We do not cache multipart range responses if strings.Contains(headers.Get("Content-Type"), "multipart/byteranges") { return false } respDir, err := cacheobject.ParseResponseCacheControl(headers.Get("Cache-Control")) if err != nil || respDir.NoCachePresent || respDir.NoStore || respDir.PrivatePresent { return false } return true }
// IsResponseCacheable returs whether the upstream server allows the requested // content to be saved in the cache. True result and 0 duration means that the // response has no expiry date. func IsResponseCacheable(resp *http.Response) (bool, time.Duration) { //!TODO: write a better custom implementation or fork the cacheobject - the API sucks //!TODO: correctly handle cache-control, pragma, etag and vary headers //!TODO: write unit tests respDir, _ := cacheobject.ParseResponseCacheControl(resp.Header.Get("Cache-Control")) return !(respDir.NoCachePresent || respDir.NoStore || respDir.PrivatePresent), 0 }
func main() { req, _ := http.NewRequest("GET", "http://www.example.com/", nil) res, _ := http.DefaultClient.Do(req) _, _ = ioutil.ReadAll(res.Body) reqDir, _ := cacheobject.ParseRequestCacheControl(req.Header.Get("Cache-Control")) resDir, _ := cacheobject.ParseResponseCacheControl(res.Header.Get("Cache-Control")) expiresHeader, _ := http.ParseTime(res.Header.Get("Expires")) dateHeader, _ := http.ParseTime(res.Header.Get("Date")) lastModifiedHeader, _ := http.ParseTime(res.Header.Get("Last-Modified")) obj := cacheobject.Object{ RespDirectives: resDir, RespHeaders: res.Header, RespStatusCode: res.StatusCode, RespExpiresHeader: expiresHeader, RespDateHeader: dateHeader, RespLastModifiedHeader: lastModifiedHeader, ReqDirectives: reqDir, ReqHeaders: req.Header, ReqMethod: req.Method, NowUTC: time.Now().UTC(), } rv := cacheobject.ObjectResults{} cacheobject.CachableObject(&obj, &rv) cacheobject.ExpirationObject(&obj, &rv) fmt.Println("Errors: ", rv.OutErr) fmt.Println("Reasons to not cache: ", rv.OutReasons) fmt.Println("Warning headers to add: ", rv.OutWarnings) fmt.Println("Expiration: ", rv.OutExpirationTime.String()) }
// ResponseExpiresIn parses the expiration time from upstream headers, if any, and returns // it as a duration from now. If no expire time is found, it returns its second argument: // the default expiration time. func ResponseExpiresIn(headers http.Header, ifNotAny time.Duration) time.Duration { //!TODO: this cacheobject.ParseResponseCacheControl is called two times for every // caceable response. We should find a way not to duplicate work. Maybe pass the resout // around somehow? respDir, err := cacheobject.ParseResponseCacheControl(headers.Get("Cache-Control")) if err != nil { return ifNotAny } if respDir.SMaxAge > 0 { return time.Duration(respDir.SMaxAge) * time.Second } else if respDir.MaxAge > 0 { return time.Duration(respDir.MaxAge) * time.Second } else if headers.Get("Expires") != "" { _ = "breakpoint" if t, err := time.Parse(time.RFC1123, headers.Get("Expires")); err == nil { //!TODO: use the server time from the Date header to calculate? return t.Sub(time.Now()) } } return ifNotAny }