Skip to content

Commit

Permalink
Push down headers from client (#1453)
Browse files Browse the repository at this point in the history
* Push down headers from client

* Push first, serve later

* After review fixes
  • Loading branch information
wendigo authored and mholt committed Feb 18, 2017
1 parent 9720da5 commit ce3580b
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 19 deletions.
61 changes: 51 additions & 10 deletions caddyhttp/push/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,16 @@ func (h Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, erro
return h.Next.ServeHTTP(w, r)
}

// Serve file first
code, err := h.Next.ServeHTTP(w, r)

if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
headers := h.filterProxiedHeaders(r.Header)

// Push first
outer:
for _, rule := range h.Rules {
if httpserver.Path(r.URL.Path).Matches(rule.Path) {
for _, resource := range rule.Resources {
pushErr := pusher.Push(resource.Path, &http.PushOptions{
Method: resource.Method,
Header: resource.Header,
Header: h.mergeHeaders(headers, resource.Header),
})
if pushErr != nil {
// If we cannot push (either not supported or concurrent streams are full - break)
Expand All @@ -44,14 +40,17 @@ outer:
}
}

// Serve later
code, err := h.Next.ServeHTTP(w, r)

if links, exists := w.Header()["Link"]; exists {
h.pushLinks(pusher, links)
h.servePreloadLinks(pusher, headers, links)
}

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:
for _, link := range links {
parts := strings.Split(link, ";")
Expand All @@ -62,9 +61,51 @@ outer:

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 {
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",
}
65 changes: 56 additions & 9 deletions caddyhttp/push/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,51 @@ func TestMiddlewareWillPushResources(t *testing.T) {

"/index2.css": {
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"}},
},
}

Expand Down Expand Up @@ -169,7 +213,7 @@ func TestMiddlewareWillNotPushResources(t *testing.T) {

// then
if err2 != nil {
t.Errorf("Should not return error")
t.Error("Should not return error")
}
}

Expand Down Expand Up @@ -201,21 +245,21 @@ func TestMiddlewareShouldInterceptLinkHeader(t *testing.T) {

// then
if err2 != nil {
t.Errorf("Should not return error")
t.Error("Should not return error")
}

expectedPushedResources := map[string]*http.PushOptions{
"/index.css": {
Method: http.MethodGet,
Header: nil,
Header: http.Header{},
},
"/index2.css": {
Method: http.MethodGet,
Header: nil,
Header: http.Header{},
},
"/index3.css": {
Method: http.MethodGet,
Header: nil,
Header: http.Header{},
},
}

Expand All @@ -224,7 +268,10 @@ func TestMiddlewareShouldInterceptLinkHeader(t *testing.T) {

func TestMiddlewareShouldInterceptLinkHeaderPusherError(t *testing.T) {
// given
expectedHeaders := http.Header{"Accept-Encoding": []string{"br"}}
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 {
Expand All @@ -247,13 +294,13 @@ func TestMiddlewareShouldInterceptLinkHeaderPusherError(t *testing.T) {

// then
if err2 != nil {
t.Errorf("Should not return error")
t.Error("Should not return error")
}

expectedPushedResources := map[string]*http.PushOptions{
"/index.css": {
Method: http.MethodGet,
Header: nil,
Header: expectedHeaders,
},
}

Expand All @@ -273,7 +320,7 @@ func comparePushedResources(t *testing.T, expected, actual map[string]*http.Push
}

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 {
t.Errorf("Expected %s to be pushed", target)
Expand Down

0 comments on commit ce3580b

Please sign in to comment.