131 lines
2.6 KiB
Go
131 lines
2.6 KiB
Go
|
package http
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"net/http"
|
||
|
"net/url"
|
||
|
"path"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/coreos/dex/pkg/log"
|
||
|
)
|
||
|
|
||
|
func WriteError(w http.ResponseWriter, code int, msg string) {
|
||
|
e := struct {
|
||
|
Error string `json:"error"`
|
||
|
}{
|
||
|
Error: msg,
|
||
|
}
|
||
|
b, err := json.Marshal(e)
|
||
|
if err != nil {
|
||
|
log.Errorf("Failed marshaling %#v to JSON: %v", e, err)
|
||
|
}
|
||
|
w.Header().Set("Content-Type", "application/json")
|
||
|
w.WriteHeader(code)
|
||
|
w.Write(b)
|
||
|
}
|
||
|
|
||
|
func cacheControlMaxAge(hdr string) (time.Duration, bool, error) {
|
||
|
for _, field := range strings.Split(hdr, ",") {
|
||
|
parts := strings.SplitN(strings.TrimSpace(field), "=", 2)
|
||
|
k := strings.ToLower(strings.TrimSpace(parts[0]))
|
||
|
if k != "max-age" {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if len(parts) == 1 {
|
||
|
return 0, false, errors.New("max-age has no value")
|
||
|
}
|
||
|
|
||
|
v := strings.TrimSpace(parts[1])
|
||
|
if v == "" {
|
||
|
return 0, false, errors.New("max-age has empty value")
|
||
|
}
|
||
|
|
||
|
age, err := strconv.Atoi(v)
|
||
|
if err != nil {
|
||
|
return 0, false, err
|
||
|
}
|
||
|
|
||
|
if age <= 0 {
|
||
|
return 0, false, nil
|
||
|
}
|
||
|
|
||
|
return time.Duration(age) * time.Second, true, nil
|
||
|
}
|
||
|
|
||
|
return 0, false, nil
|
||
|
}
|
||
|
|
||
|
func expires(date, expires string) (time.Duration, bool, error) {
|
||
|
if date == "" || expires == "" {
|
||
|
return 0, false, nil
|
||
|
}
|
||
|
|
||
|
te, err := time.Parse(time.RFC1123, expires)
|
||
|
if err != nil {
|
||
|
return 0, false, err
|
||
|
}
|
||
|
|
||
|
td, err := time.Parse(time.RFC1123, date)
|
||
|
if err != nil {
|
||
|
return 0, false, err
|
||
|
}
|
||
|
|
||
|
ttl := te.Sub(td)
|
||
|
|
||
|
// headers indicate data already expired, caller should not
|
||
|
// have to care about this case
|
||
|
if ttl <= 0 {
|
||
|
return 0, false, nil
|
||
|
}
|
||
|
|
||
|
return ttl, true, nil
|
||
|
}
|
||
|
|
||
|
func Cacheable(hdr http.Header) (time.Duration, bool, error) {
|
||
|
ttl, ok, err := cacheControlMaxAge(hdr.Get("Cache-Control"))
|
||
|
if err != nil || ok {
|
||
|
return ttl, ok, err
|
||
|
}
|
||
|
|
||
|
return expires(hdr.Get("Date"), hdr.Get("Expires"))
|
||
|
}
|
||
|
|
||
|
// MergeQuery appends additional query values to an existing URL.
|
||
|
func MergeQuery(u url.URL, q url.Values) url.URL {
|
||
|
uv := u.Query()
|
||
|
for k, vs := range q {
|
||
|
for _, v := range vs {
|
||
|
uv.Add(k, v)
|
||
|
}
|
||
|
}
|
||
|
u.RawQuery = uv.Encode()
|
||
|
return u
|
||
|
}
|
||
|
|
||
|
// NewResourceLocation appends a resource id to the end of the requested URL path.
|
||
|
func NewResourceLocation(reqURL *url.URL, id string) string {
|
||
|
var u url.URL
|
||
|
u = *reqURL
|
||
|
u.Path = path.Join(u.Path, id)
|
||
|
u.RawQuery = ""
|
||
|
u.Fragment = ""
|
||
|
return u.String()
|
||
|
}
|
||
|
|
||
|
// CopyRequest returns a clone of the provided *http.Request.
|
||
|
// The returned object is a shallow copy of the struct and a
|
||
|
// deep copy of its Header field.
|
||
|
func CopyRequest(r *http.Request) *http.Request {
|
||
|
r2 := *r
|
||
|
r2.Header = make(http.Header)
|
||
|
for k, s := range r.Header {
|
||
|
r2.Header[k] = s
|
||
|
}
|
||
|
return &r2
|
||
|
}
|