diff --git a/README.md b/README.md index d3c466b..ebb0ed6 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ handler into larger Go servers. ``` host: example.com +cache_age: 3600 paths: /foo: repo: https://github.com/example/foo @@ -75,6 +76,11 @@ paths: + + cache_age + optional + The amount of time to cache package pages in seconds. Controls the max-age directive sent in the Cache-Control HTTP header. + host optional diff --git a/handler.go b/handler.go index 6f5a3f9..897744c 100644 --- a/handler.go +++ b/handler.go @@ -16,6 +16,7 @@ package main import ( + "errors" "fmt" "html/template" "net/http" @@ -26,8 +27,9 @@ import ( ) type handler struct { - host string - paths pathConfigSet + host string + cacheControl string + paths pathConfigSet } type pathConfig struct { @@ -39,8 +41,9 @@ type pathConfig struct { func newHandler(config []byte) (*handler, error) { var parsed struct { - Host string `yaml:"host,omitempty"` - Paths map[string]struct { + Host string `yaml:"host,omitempty"` + CacheAge *int64 `yaml:"cache_max_age,omitempty"` + Paths map[string]struct { Repo string `yaml:"repo,omitempty"` Display string `yaml:"display,omitempty"` VCS string `yaml:"vcs,omitempty"` @@ -50,6 +53,14 @@ func newHandler(config []byte) (*handler, error) { return nil, err } h := &handler{host: parsed.Host} + cacheAge := int64(86400) // 24 hpurs (in seconds) + if parsed.CacheAge != nil { + cacheAge = *parsed.CacheAge + if cacheAge < 0 { + return nil, errors.New("cache_max_age is negative") + } + } + h.cacheControl = fmt.Sprintf("public, max-age=%d", cacheAge) for path, e := range parsed.Paths { pc := pathConfig{ path: strings.TrimSuffix(path, "/"), @@ -94,6 +105,7 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + w.Header().Set("Cache-Control", h.cacheControl) if err := vanityTmpl.Execute(w, struct { Import string Subpath string diff --git a/handler_test.go b/handler_test.go index 9b41c8a..9c41bf3 100644 --- a/handler_test.go +++ b/handler_test.go @@ -139,6 +139,10 @@ func TestBadConfigs(t *testing.T) { " /unknownvcs:\n" + " repo: https://bitbucket.org/zombiezen/gopdf\n" + " vcs: xyzzy\n", + "cache_max_age: -1\n" + + "paths:\n" + + " /portmidi:\n" + + " repo: https://github.com/rakyll/portmidi\n", } for _, config := range badConfigs { _, err := newHandler([]byte(config)) @@ -227,3 +231,45 @@ func TestPathConfigSetFind(t *testing.T) { } } } + +func TestCacheHeader(t *testing.T) { + tests := []struct { + name string + config string + cacheControl string + }{ + { + name: "default", + cacheControl: "public, max-age=86400", + }, + { + name: "specify time", + config: "cache_max_age: 60\n", + cacheControl: "public, max-age=60", + }, + { + name: "zero", + config: "cache_max_age: 0\n", + cacheControl: "public, max-age=0", + }, + } + for _, test := range tests { + h, err := newHandler([]byte("paths:\n /portmidi:\n repo: https://github.com/rakyll/portmidi\n" + + test.config)) + if err != nil { + t.Errorf("%s: newHandler: %v", test.name, err) + continue + } + s := httptest.NewServer(h) + resp, err := http.Get(s.URL + "/portmidi") + if err != nil { + t.Errorf("%s: http.Get: %v", test.name, err) + continue + } + resp.Body.Close() + got := resp.Header.Get("Cache-Control") + if got != test.cacheControl { + t.Errorf("%s: Cache-Control header = %q; want %q", test.name, got, test.cacheControl) + } + } +}