0
Fork 0
mirror of https://github.com/caddyserver/caddy.git synced 2024-12-23 22:27:38 -05:00
caddy/caddyhttp/push/handler.go
Mateusz Gajewski 6d7462ac99 push: Allow pushing multiple resources via Link header (#1798)
* Allow pushing multiple resources via Link header

* Add nopush test case

* Extract Link header parsing to separate function

* Parser regexp-free

* Remove dead code, thx gometalinter

* Redundant condition - won't happen

* Reduce duplication
2017-08-28 19:38:29 -06:00

129 lines
2.9 KiB
Go

package push
import (
"net/http"
"strings"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/mholt/caddy/caddyhttp/staticfiles"
)
func (h Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
pusher, hasPusher := w.(http.Pusher)
// no push possible, carry on
if !hasPusher {
return h.Next.ServeHTTP(w, r)
}
// check if this is a request for the pushed resource (avoid recursion)
if _, exists := r.Header[pushHeader]; exists {
return h.Next.ServeHTTP(w, r)
}
headers := h.filterProxiedHeaders(r.Header)
// push first
outer:
for _, rule := range h.Rules {
urlPath := r.URL.Path
matches := httpserver.Path(urlPath).Matches(rule.Path)
// Also check IndexPages when requesting a directory
if !matches {
_, matches = httpserver.IndexFile(h.Root, urlPath, staticfiles.IndexPages)
}
if matches {
for _, resource := range rule.Resources {
pushErr := pusher.Push(resource.Path, &http.PushOptions{
Method: resource.Method,
Header: h.mergeHeaders(headers, resource.Header),
})
if pushErr != nil {
// if we cannot push (either not supported or concurrent streams are full - break)
break outer
}
}
}
}
// serve later
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 {
h.servePreloadLinks(pusher, headers, links)
}
return code, err
}
// servePreloadLinks parses Link headers from backend and pushes resources found in them.
// For accepted header formats check parseLinkHeader function.
//
// If resource has 'nopush' attribute then it will be omitted.
func (h Middleware) servePreloadLinks(pusher http.Pusher, headers http.Header, resources []string) {
outer:
for _, resource := range resources {
for _, resource := range parseLinkHeader(resource) {
if _, exists := resource.params["nopush"]; exists {
continue
}
if h.isRemoteResource(resource.uri) {
continue
}
err := pusher.Push(resource.uri, &http.PushOptions{
Method: http.MethodGet,
Header: headers,
})
if err != nil {
break outer
}
}
}
}
func (h Middleware) isRemoteResource(resource string) bool {
return strings.HasPrefix(resource, "//") ||
strings.HasPrefix(resource, "http://") ||
strings.HasPrefix(resource, "https://")
}
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",
}