mirror of
https://github.com/umputun/reproxy.git
synced 2024-12-01 19:32:16 +03:00
5743109210
* add support of html error reporting with custom templates * typo * formatting * better template load error msg
129 lines
3.6 KiB
Go
129 lines
3.6 KiB
Go
package proxy
|
|
|
|
import (
|
|
"fmt"
|
|
"mime"
|
|
"net/http"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// CacheControl sets Cache-Control response header with different ages for different mimes
|
|
type CacheControl struct {
|
|
defaultMaxAge time.Duration
|
|
maxAges map[string]time.Duration
|
|
}
|
|
|
|
// NewCacheControl creates NewCacheControl with the default max age
|
|
func NewCacheControl(defaultAge time.Duration) *CacheControl {
|
|
return &CacheControl{defaultMaxAge: defaultAge, maxAges: map[string]time.Duration{}}
|
|
}
|
|
|
|
// AddMime sets max age for a given mime
|
|
func (c *CacheControl) AddMime(m string, d time.Duration) {
|
|
c.maxAges[m] = d
|
|
}
|
|
|
|
// Middleware checks if mime custom age set and returns it if matched to content type from resource (file) extension.
|
|
// fallback to default if nothing matched
|
|
func (c *CacheControl) Middleware(next http.Handler) http.Handler {
|
|
|
|
setMaxAgeHeader := func(age time.Duration, w http.ResponseWriter) {
|
|
w.Header().Set("Cache-Control", "public, max-age="+strconv.Itoa(int(age.Seconds())))
|
|
}
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if len(c.maxAges) == 0 && c.defaultMaxAge == 0 { // cache control disabled
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
if len(c.maxAges) == 0 && c.defaultMaxAge > 0 {
|
|
setMaxAgeHeader(c.defaultMaxAge, w)
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
ext := path.Ext(r.URL.Path) // the extension ext should begin with a leading dot, as in ".html"
|
|
if ext == "" {
|
|
ext = ".html"
|
|
}
|
|
mt := mime.TypeByExtension(ext)
|
|
if elems := strings.Split(mt, ";"); len(elems) > 1 { // strip suffix after ";", i.e. text/html; charset=utf-8
|
|
mt = strings.TrimSpace(elems[0])
|
|
}
|
|
val := c.defaultMaxAge
|
|
if v, ok := c.maxAges[mt]; ok {
|
|
val = v
|
|
}
|
|
setMaxAgeHeader(val, w)
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
// MakeCacheControl creates CacheControl from the list of params.
|
|
// the first param represents default age and can be just a duration string (i.e. 60h) or "default:60h"
|
|
// all other params are mime:duration pairs, i.e. "text/html:30s"
|
|
func MakeCacheControl(cacheOpts []string) (*CacheControl, error) {
|
|
|
|
parseDuration := func(s string) (time.Duration, error) {
|
|
if strings.HasSuffix(s, "d") { // add parsing 123d as days
|
|
days, err := strconv.Atoi(strings.TrimSuffix(s, "d"))
|
|
if err != nil {
|
|
return 0, fmt.Errorf("can't parse %q as duration: %w", s, err)
|
|
}
|
|
return time.Hour * 24 * time.Duration(days), nil
|
|
}
|
|
return time.ParseDuration(s)
|
|
}
|
|
|
|
if len(cacheOpts) == 0 {
|
|
return NewCacheControl(0), nil
|
|
}
|
|
res := NewCacheControl(0)
|
|
|
|
// first elements may define default in both "10s" and "default:10s" forms
|
|
if !strings.Contains(cacheOpts[0], ":") { // single element, i.e 10s
|
|
dur, err := parseDuration(cacheOpts[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't parse default cache duration: %w", err)
|
|
}
|
|
res = NewCacheControl(dur)
|
|
}
|
|
|
|
if strings.Contains(cacheOpts[0], ":") { // two elements, i.e default:10s
|
|
elems := strings.Split(cacheOpts[0], ":")
|
|
if elems[0] != "default" {
|
|
return nil, fmt.Errorf("first cache duration has to be for the default mime")
|
|
}
|
|
|
|
dur, err := parseDuration(elems[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't parse default cache duration: %w", err)
|
|
}
|
|
|
|
res = NewCacheControl(dur)
|
|
}
|
|
|
|
// default only, no mime types
|
|
if len(cacheOpts) == 1 {
|
|
return res, nil
|
|
}
|
|
|
|
for _, v := range cacheOpts[1:] {
|
|
elems := strings.Split(v, ":")
|
|
if len(elems) != 2 {
|
|
return nil, fmt.Errorf("invalid mime:age entry %q", v)
|
|
}
|
|
dur, err := time.ParseDuration(elems[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't parse cache duration from %s: %w", v, err)
|
|
}
|
|
res.AddMime(elems[0], dur)
|
|
}
|
|
return res, nil
|
|
}
|