mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-23 22:27:38 -05:00
Push down headers from client (#1453)
* Push down headers from client * Push first, serve later * After review fixes
This commit is contained in:
parent
9720da5bc8
commit
ce3580bf91
2 changed files with 107 additions and 19 deletions
|
@ -21,20 +21,16 @@ func (h Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, erro
|
||||||
return h.Next.ServeHTTP(w, r)
|
return h.Next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve file first
|
headers := h.filterProxiedHeaders(r.Header)
|
||||||
code, err := h.Next.ServeHTTP(w, r)
|
|
||||||
|
|
||||||
if flusher, ok := w.(http.Flusher); ok {
|
|
||||||
flusher.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Push first
|
||||||
outer:
|
outer:
|
||||||
for _, rule := range h.Rules {
|
for _, rule := range h.Rules {
|
||||||
if httpserver.Path(r.URL.Path).Matches(rule.Path) {
|
if httpserver.Path(r.URL.Path).Matches(rule.Path) {
|
||||||
for _, resource := range rule.Resources {
|
for _, resource := range rule.Resources {
|
||||||
pushErr := pusher.Push(resource.Path, &http.PushOptions{
|
pushErr := pusher.Push(resource.Path, &http.PushOptions{
|
||||||
Method: resource.Method,
|
Method: resource.Method,
|
||||||
Header: resource.Header,
|
Header: h.mergeHeaders(headers, resource.Header),
|
||||||
})
|
})
|
||||||
if pushErr != nil {
|
if pushErr != nil {
|
||||||
// If we cannot push (either not supported or concurrent streams are full - break)
|
// If we cannot push (either not supported or concurrent streams are full - break)
|
||||||
|
@ -44,14 +40,17 @@ outer:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Serve later
|
||||||
|
code, err := h.Next.ServeHTTP(w, r)
|
||||||
|
|
||||||
if links, exists := w.Header()["Link"]; exists {
|
if links, exists := w.Header()["Link"]; exists {
|
||||||
h.pushLinks(pusher, links)
|
h.servePreloadLinks(pusher, headers, links)
|
||||||
}
|
}
|
||||||
|
|
||||||
return code, err
|
return code, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Middleware) pushLinks(pusher http.Pusher, links []string) {
|
func (h Middleware) servePreloadLinks(pusher http.Pusher, headers http.Header, links []string) {
|
||||||
outer:
|
outer:
|
||||||
for _, link := range links {
|
for _, link := range links {
|
||||||
parts := strings.Split(link, ";")
|
parts := strings.Split(link, ";")
|
||||||
|
@ -62,9 +61,51 @@ outer:
|
||||||
|
|
||||||
target := strings.TrimSuffix(strings.TrimPrefix(parts[0], "<"), ">")
|
target := strings.TrimSuffix(strings.TrimPrefix(parts[0], "<"), ">")
|
||||||
|
|
||||||
err := pusher.Push(target, &http.PushOptions{Method: http.MethodGet})
|
err := pusher.Push(target, &http.PushOptions{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Header: headers,
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break outer
|
break outer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h Middleware) mergeHeaders(l, r http.Header) http.Header {
|
||||||
|
|
||||||
|
out := http.Header{}
|
||||||
|
|
||||||
|
for k, v := range l {
|
||||||
|
out[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, vv := range r {
|
||||||
|
for _, v := range vv {
|
||||||
|
out.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Middleware) filterProxiedHeaders(headers http.Header) http.Header {
|
||||||
|
|
||||||
|
filter := http.Header{}
|
||||||
|
|
||||||
|
for _, header := range proxiedHeaders {
|
||||||
|
if val, ok := headers[header]; ok {
|
||||||
|
filter[header] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter
|
||||||
|
}
|
||||||
|
|
||||||
|
var proxiedHeaders = []string{
|
||||||
|
"Accept-Encoding",
|
||||||
|
"Accept-Language",
|
||||||
|
"Cache-Control",
|
||||||
|
"Host",
|
||||||
|
"User-Agent",
|
||||||
|
}
|
||||||
|
|
|
@ -61,7 +61,51 @@ func TestMiddlewareWillPushResources(t *testing.T) {
|
||||||
|
|
||||||
"/index2.css": {
|
"/index2.css": {
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
Header: nil,
|
Header: http.Header{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
comparePushedResources(t, expectedPushedResources, pushingWriter.pushed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMiddlewareWillPushResourcesWithMergedHeaders(t *testing.T) {
|
||||||
|
|
||||||
|
// given
|
||||||
|
request, err := http.NewRequest(http.MethodGet, "/index.html", nil)
|
||||||
|
request.Header = http.Header{"Accept-Encoding": []string{"br"}, "Invalid-Header": []string{"Should be filter out"}}
|
||||||
|
writer := httptest.NewRecorder()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create HTTP request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
middleware := Middleware{
|
||||||
|
Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
return 0, nil
|
||||||
|
}),
|
||||||
|
Rules: []Rule{
|
||||||
|
{Path: "/index.html", Resources: []Resource{
|
||||||
|
{Path: "/index.css", Method: http.MethodHead, Header: http.Header{"Test": []string{"Value"}}},
|
||||||
|
{Path: "/index2.css", Method: http.MethodGet},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pushingWriter := &MockedPusher{ResponseWriter: writer}
|
||||||
|
|
||||||
|
// when
|
||||||
|
middleware.ServeHTTP(pushingWriter, request)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expectedPushedResources := map[string]*http.PushOptions{
|
||||||
|
"/index.css": {
|
||||||
|
Method: http.MethodHead,
|
||||||
|
Header: http.Header{"Test": []string{"Value"}, "Accept-Encoding": []string{"br"}},
|
||||||
|
},
|
||||||
|
|
||||||
|
"/index2.css": {
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Header: http.Header{"Accept-Encoding": []string{"br"}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +213,7 @@ func TestMiddlewareWillNotPushResources(t *testing.T) {
|
||||||
|
|
||||||
// then
|
// then
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
t.Errorf("Should not return error")
|
t.Error("Should not return error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,21 +245,21 @@ func TestMiddlewareShouldInterceptLinkHeader(t *testing.T) {
|
||||||
|
|
||||||
// then
|
// then
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
t.Errorf("Should not return error")
|
t.Error("Should not return error")
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedPushedResources := map[string]*http.PushOptions{
|
expectedPushedResources := map[string]*http.PushOptions{
|
||||||
"/index.css": {
|
"/index.css": {
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
Header: nil,
|
Header: http.Header{},
|
||||||
},
|
},
|
||||||
"/index2.css": {
|
"/index2.css": {
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
Header: nil,
|
Header: http.Header{},
|
||||||
},
|
},
|
||||||
"/index3.css": {
|
"/index3.css": {
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
Header: nil,
|
Header: http.Header{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +268,10 @@ func TestMiddlewareShouldInterceptLinkHeader(t *testing.T) {
|
||||||
|
|
||||||
func TestMiddlewareShouldInterceptLinkHeaderPusherError(t *testing.T) {
|
func TestMiddlewareShouldInterceptLinkHeaderPusherError(t *testing.T) {
|
||||||
// given
|
// given
|
||||||
|
expectedHeaders := http.Header{"Accept-Encoding": []string{"br"}}
|
||||||
request, err := http.NewRequest(http.MethodGet, "/index.html", nil)
|
request, err := http.NewRequest(http.MethodGet, "/index.html", nil)
|
||||||
|
request.Header = http.Header{"Accept-Encoding": []string{"br"}, "Invalid-Header": []string{"Should be filter out"}}
|
||||||
|
|
||||||
writer := httptest.NewRecorder()
|
writer := httptest.NewRecorder()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -247,13 +294,13 @@ func TestMiddlewareShouldInterceptLinkHeaderPusherError(t *testing.T) {
|
||||||
|
|
||||||
// then
|
// then
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
t.Errorf("Should not return error")
|
t.Error("Should not return error")
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedPushedResources := map[string]*http.PushOptions{
|
expectedPushedResources := map[string]*http.PushOptions{
|
||||||
"/index.css": {
|
"/index.css": {
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
Header: nil,
|
Header: expectedHeaders,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,7 +320,7 @@ func comparePushedResources(t *testing.T, expected, actual map[string]*http.Push
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(expectedTarget.Header, actualTarget.Header) {
|
if !reflect.DeepEqual(expectedTarget.Header, actualTarget.Header) {
|
||||||
t.Errorf("Expected %s resource push headers to be %v, actual: %v", target, expectedTarget.Header, actualTarget.Header)
|
t.Errorf("Expected %s resource push headers to be %+v, actual: %+v", target, expectedTarget.Header, actualTarget.Header)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.Errorf("Expected %s to be pushed", target)
|
t.Errorf("Expected %s to be pushed", target)
|
||||||
|
|
Loading…
Reference in a new issue