github.com/fluxcd/go-git-providers@v0.19.3/gitprovider/cache/httpcache.go (about) 1 /* 2 Copyright 2020 The Flux CD contributors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package cache 18 19 import ( 20 "net/http" 21 22 "github.com/gregjones/httpcache" 23 ) 24 25 // TODO: Implement an unit test for this package. 26 27 // NewHTTPCacheTransport is a gitprovider.ChainableRoundTripperFunc which adds 28 // HTTP Conditional Requests caching for the backend, if the server supports it. 29 func NewHTTPCacheTransport(in http.RoundTripper) http.RoundTripper { 30 // Create a new httpcache high-level Transport 31 t := httpcache.NewMemoryCacheTransport() 32 // Configure the httpcache Transport to use in as its underlying Transport. 33 // If in is nil, http.DefaultTransport will be used. 34 t.Transport = in 35 // Set "out" to use a slightly custom variant of the httpcache Transport 36 // (with more aggressive cache invalidation) 37 return &cacheRoundtripper{Transport: t} 38 } 39 40 // cacheRoundtripper is a slight wrapper around *httpcache.Transport that automatically 41 // invalidates the cache on non-GET/HEAD requests, and non-"200 OK" responses. 42 type cacheRoundtripper struct { 43 Transport *httpcache.Transport 44 } 45 46 // This function follows the same logic as in github.com/gregjones/httpcache to be able 47 // to implement our custom roundtripper logic below. 48 func cacheKey(req *http.Request) string { 49 if req.Method == http.MethodGet { 50 return req.URL.String() 51 } 52 return req.Method + " " + req.URL.String() 53 } 54 55 // RoundTrip calls the underlying RoundTrip (using the cache), but invalidates the cache on 56 // non GET/HEAD requests and non-"200 OK" responses. 57 func (r *cacheRoundtripper) RoundTrip(req *http.Request) (*http.Response, error) { 58 // These two statements are the same as in github.com/gregjones/httpcache Transport.RoundTrip 59 // to be able to implement our custom roundtripper below 60 cacheKey := cacheKey(req) 61 cacheable := (req.Method == "GET" || req.Method == "HEAD") && req.Header.Get("range") == "" 62 63 // If the object isn't a GET or HEAD request, also invalidate the cache of the GET URL 64 // as this action will modify the underlying resource (e.g. DELETE/POST/PATCH) 65 if !cacheable { 66 r.Transport.Cache.Delete(req.URL.String()) 67 } 68 // Call the underlying roundtrip 69 resp, err := r.Transport.RoundTrip(req) 70 // Don't cache anything but "200 OK" requests 71 if resp == nil || resp.StatusCode != http.StatusOK { 72 r.Transport.Cache.Delete(cacheKey) 73 } 74 return resp, err 75 }