Commit ce2a9cd8 authored by Matthew Holt's avatar Matthew Holt

push: Reorder before proxy; and allow zero arguments (cf. #1573)

parent 4462e397
...@@ -475,11 +475,11 @@ var directives = []string{ ...@@ -475,11 +475,11 @@ var directives = []string{
"internal", "internal",
"pprof", "pprof",
"expvar", "expvar",
"push",
"prometheus", // github.com/miekg/caddy-prometheus "prometheus", // github.com/miekg/caddy-prometheus
"proxy", "proxy",
"fastcgi", "fastcgi",
"cgi", // github.com/jung-kurt/caddy-cgi "cgi", // github.com/jung-kurt/caddy-cgi
"push",
"websocket", "websocket",
"filemanager", // github.com/hacdias/caddy-filemanager "filemanager", // github.com/hacdias/caddy-filemanager
"markdown", "markdown",
......
...@@ -8,22 +8,21 @@ import ( ...@@ -8,22 +8,21 @@ import (
) )
func (h Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { func (h Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
pusher, hasPusher := w.(http.Pusher) pusher, hasPusher := w.(http.Pusher)
// No Pusher, no cry // no push possible, carry on
if !hasPusher { if !hasPusher {
return h.Next.ServeHTTP(w, r) return h.Next.ServeHTTP(w, r)
} }
// This is request for the pushed resource - it should not be recursive // check if this is a request for the pushed resource (avoid recursion)
if _, exists := r.Header[pushHeader]; exists { if _, exists := r.Header[pushHeader]; exists {
return h.Next.ServeHTTP(w, r) return h.Next.ServeHTTP(w, r)
} }
headers := h.filterProxiedHeaders(r.Header) headers := h.filterProxiedHeaders(r.Header)
// Push first // 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) {
...@@ -33,16 +32,17 @@ outer: ...@@ -33,16 +32,17 @@ outer:
Header: h.mergeHeaders(headers, 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)
break outer break outer
} }
} }
} }
} }
// Serve later // serve later
code, err := h.Next.ServeHTTP(w, r) code, err := h.Next.ServeHTTP(w, r)
// push resources returned in Link headers from upstream middlewares or proxied apps
if links, exists := w.Header()["Link"]; exists { if links, exists := w.Header()["Link"]; exists {
h.servePreloadLinks(pusher, headers, links) h.servePreloadLinks(pusher, headers, links)
} }
...@@ -51,7 +51,6 @@ outer: ...@@ -51,7 +51,6 @@ outer:
} }
func (h Middleware) servePreloadLinks(pusher http.Pusher, headers http.Header, links []string) { func (h Middleware) servePreloadLinks(pusher http.Pusher, headers http.Header, links []string) {
outer:
for _, link := range links { for _, link := range links {
parts := strings.Split(link, ";") parts := strings.Split(link, ";")
...@@ -67,13 +66,12 @@ outer: ...@@ -67,13 +66,12 @@ outer:
}) })
if err != nil { if err != nil {
break outer break
} }
} }
} }
func (h Middleware) mergeHeaders(l, r http.Header) http.Header { func (h Middleware) mergeHeaders(l, r http.Header) http.Header {
out := http.Header{} out := http.Header{}
for k, v := range l { for k, v := range l {
...@@ -90,7 +88,6 @@ func (h Middleware) mergeHeaders(l, r http.Header) http.Header { ...@@ -90,7 +88,6 @@ func (h Middleware) mergeHeaders(l, r http.Header) http.Header {
} }
func (h Middleware) filterProxiedHeaders(headers http.Header) http.Header { func (h Middleware) filterProxiedHeaders(headers http.Header) http.Header {
filter := http.Header{} filter := http.Header{}
for _, header := range proxiedHeaders { for _, header := range proxiedHeaders {
......
...@@ -45,87 +45,94 @@ func parsePushRules(c *caddy.Controller) ([]Rule, error) { ...@@ -45,87 +45,94 @@ func parsePushRules(c *caddy.Controller) ([]Rule, error) {
var rules = make(map[string]*Rule) var rules = make(map[string]*Rule)
for c.NextLine() { for c.NextLine() {
if !c.NextArg() {
return emptyRules, c.ArgErr()
}
path := c.Val()
args := c.RemainingArgs()
var rule *Rule var rule *Rule
var resources []Resource var resources []Resource
var ops []ruleOp var ops []ruleOp
if existingRule, ok := rules[path]; ok { parseBlock := func() error {
rule = existingRule for c.NextBlock() {
} else { val := c.Val()
rule = new(Rule)
rule.Path = path
rules[rule.Path] = rule
}
for i := 0; i < len(args); i++ { switch val {
resources = append(resources, Resource{ case "method":
Path: args[i], if !c.NextArg() {
Method: http.MethodGet, return c.ArgErr()
Header: http.Header{pushHeader: []string{}}, }
})
}
for c.NextBlock() { method := c.Val()
val := c.Val()
switch val { if err := validateMethod(method); err != nil {
case "method": return errMethodNotSupported
if !c.NextArg() { }
return emptyRules, c.ArgErr()
}
method := c.Val() ops = append(ops, setMethodOp(method))
if err := validateMethod(method); err != nil { case "header":
return emptyRules, errMethodNotSupported args := c.RemainingArgs()
}
ops = append(ops, setMethodOp(method)) if len(args) != 2 {
return errInvalidHeader
}
case "header": if err := validateHeader(args[0]); err != nil {
args := c.RemainingArgs() return err
}
if len(args) != 2 { ops = append(ops, setHeaderOp(args[0], args[1]))
return emptyRules, errInvalidHeader default:
resources = append(resources, Resource{
Path: val,
Method: http.MethodGet,
Header: http.Header{pushHeader: []string{}},
})
} }
}
return nil
}
if err := validateHeader(args[0]); err != nil { args := c.RemainingArgs()
return emptyRules, err
}
ops = append(ops, setHeaderOp(args[0], args[1])) if len(args) == 0 {
rule = new(Rule)
rule.Path = "/"
rules["/"] = rule
err := parseBlock()
if err != nil {
return emptyRules, err
}
} else {
path := args[0]
if existingRule, ok := rules[path]; ok {
rule = existingRule
} else {
rule = new(Rule)
rule.Path = path
rules[rule.Path] = rule
}
default: for i := 1; i < len(args); i++ {
resources = append(resources, Resource{ resources = append(resources, Resource{
Path: val, Path: args[i],
Method: http.MethodGet, Method: http.MethodGet,
Header: http.Header{pushHeader: []string{}}, Header: http.Header{pushHeader: []string{}},
}) })
} }
err := parseBlock()
if err != nil {
return emptyRules, err
}
} }
for _, op := range ops { for _, op := range ops {
op(resources) op(resources)
} }
rule.Resources = append(rule.Resources, resources...) rule.Resources = append(rule.Resources, resources...)
} }
var returnRules []Rule var returnRules []Rule
for _, rule := range rules {
for path, rule := range rules {
if len(rule.Resources) == 0 {
return emptyRules, c.Errf("Rule %s has empty push resources list", path)
}
returnRules = append(returnRules, *rule) returnRules = append(returnRules, *rule)
} }
...@@ -141,7 +148,6 @@ func setHeaderOp(key, value string) func(resources []Resource) { ...@@ -141,7 +148,6 @@ func setHeaderOp(key, value string) func(resources []Resource) {
} }
func setMethodOp(method string) func(resources []Resource) { func setMethodOp(method string) func(resources []Resource) {
return func(resources []Resource) { return func(resources []Resource) {
for index := range resources { for index := range resources {
resources[index].Method = method resources[index].Method = method
......
...@@ -25,10 +25,10 @@ func TestConfigParse(t *testing.T) { ...@@ -25,10 +25,10 @@ func TestConfigParse(t *testing.T) {
expected []Rule expected []Rule
}{ }{
{ {
"ParseInvalidEmptyConfig", `push`, true, []Rule{}, "ParseInvalidEmptyConfig", `push`, false, []Rule{{Path: "/"}},
}, },
{ {
"ParseInvalidConfig", `push /index.html`, true, []Rule{}, "ParseInvalidConfig", `push /index.html`, false, []Rule{{Path: "/index.html"}},
}, },
{ {
"ParseInvalidConfigBlock", `push /index.html /index.css { "ParseInvalidConfigBlock", `push /index.html /index.css {
...@@ -255,7 +255,7 @@ func TestSetupInstalledMiddleware(t *testing.T) { ...@@ -255,7 +255,7 @@ func TestSetupInstalledMiddleware(t *testing.T) {
func TestSetupWithError(t *testing.T) { func TestSetupWithError(t *testing.T) {
// given // given
c := caddy.NewTestController("http", `push /index.html`) c := caddy.NewTestController("http", "push {\nmethod\n}")
// when // when
err := setup(c) err := setup(c)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment