2017-09-23 13:56:58 +08:00
|
|
|
// Copyright 2015 Light Code Labs, LLC
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2017-02-18 00:25:22 +08:00
|
|
|
package push
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
|
2019-07-03 02:49:20 +08:00
|
|
|
"github.com/caddyserver/caddy/caddyhttp/httpserver"
|
2017-02-18 00:25:22 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
func (h Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
|
|
|
pusher, hasPusher := w.(http.Pusher)
|
|
|
|
|
2017-04-18 12:06:17 +08:00
|
|
|
// no push possible, carry on
|
2017-02-18 00:25:22 +08:00
|
|
|
if !hasPusher {
|
|
|
|
return h.Next.ServeHTTP(w, r)
|
|
|
|
}
|
|
|
|
|
2017-04-18 12:06:17 +08:00
|
|
|
// check if this is a request for the pushed resource (avoid recursion)
|
2017-02-18 00:25:22 +08:00
|
|
|
if _, exists := r.Header[pushHeader]; exists {
|
|
|
|
return h.Next.ServeHTTP(w, r)
|
|
|
|
}
|
|
|
|
|
2017-02-19 06:50:36 +08:00
|
|
|
headers := h.filterProxiedHeaders(r.Header)
|
2017-02-18 00:25:22 +08:00
|
|
|
|
2017-04-18 12:06:17 +08:00
|
|
|
// push first
|
2017-02-18 00:25:22 +08:00
|
|
|
outer:
|
|
|
|
for _, rule := range h.Rules {
|
2017-07-06 15:24:01 +08:00
|
|
|
urlPath := r.URL.Path
|
|
|
|
matches := httpserver.Path(urlPath).Matches(rule.Path)
|
|
|
|
// Also check IndexPages when requesting a directory
|
|
|
|
if !matches {
|
2017-10-30 05:13:10 +08:00
|
|
|
indexFile, isIndexFile := httpserver.IndexFile(h.Root, urlPath, h.indexPages)
|
2017-09-04 21:53:41 +08:00
|
|
|
if isIndexFile {
|
|
|
|
matches = httpserver.Path(indexFile).Matches(rule.Path)
|
|
|
|
}
|
2017-07-06 15:24:01 +08:00
|
|
|
}
|
|
|
|
if matches {
|
2017-02-18 00:25:22 +08:00
|
|
|
for _, resource := range rule.Resources {
|
|
|
|
pushErr := pusher.Push(resource.Path, &http.PushOptions{
|
|
|
|
Method: resource.Method,
|
2017-02-19 06:50:36 +08:00
|
|
|
Header: h.mergeHeaders(headers, resource.Header),
|
2017-02-18 00:25:22 +08:00
|
|
|
})
|
|
|
|
if pushErr != nil {
|
2017-04-18 12:06:17 +08:00
|
|
|
// if we cannot push (either not supported or concurrent streams are full - break)
|
2017-02-18 00:25:22 +08:00
|
|
|
break outer
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-18 12:06:17 +08:00
|
|
|
// serve later
|
2017-02-19 06:50:36 +08:00
|
|
|
code, err := h.Next.ServeHTTP(w, r)
|
|
|
|
|
2017-04-18 12:06:17 +08:00
|
|
|
// push resources returned in Link headers from upstream middlewares or proxied apps
|
2017-02-18 00:25:22 +08:00
|
|
|
if links, exists := w.Header()["Link"]; exists {
|
2017-02-19 06:50:36 +08:00
|
|
|
h.servePreloadLinks(pusher, headers, links)
|
2017-02-18 00:25:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return code, err
|
|
|
|
}
|
|
|
|
|
2017-08-29 09:38:29 +08:00
|
|
|
// 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
|
|
|
|
}
|
2017-02-18 00:25:22 +08:00
|
|
|
|
2017-08-29 09:38:29 +08:00
|
|
|
if h.isRemoteResource(resource.uri) {
|
|
|
|
continue
|
|
|
|
}
|
2017-02-18 00:25:22 +08:00
|
|
|
|
2017-08-29 09:38:29 +08:00
|
|
|
err := pusher.Push(resource.uri, &http.PushOptions{
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Header: headers,
|
|
|
|
})
|
2017-02-19 06:50:36 +08:00
|
|
|
|
2017-08-29 09:38:29 +08:00
|
|
|
if err != nil {
|
|
|
|
break outer
|
|
|
|
}
|
2017-02-18 00:25:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-02-19 06:50:36 +08:00
|
|
|
|
2017-08-29 09:38:29 +08:00
|
|
|
func (h Middleware) isRemoteResource(resource string) bool {
|
|
|
|
return strings.HasPrefix(resource, "//") ||
|
|
|
|
strings.HasPrefix(resource, "http://") ||
|
|
|
|
strings.HasPrefix(resource, "https://")
|
|
|
|
}
|
|
|
|
|
2017-02-19 06:50:36 +08:00
|
|
|
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",
|
|
|
|
}
|