func isRemoteResource(resource string) bool {
	return strings.HasPrefix(resource, "//") ||
		strings.HasPrefix(resource, "http://") ||
		strings.HasPrefix(resource, "https://")
}
isRemoteResource returns true if resource starts with
a scheme or is a protocol-relative URI.
package push
import (
	"fmt"
	"net/http"
	"strings"
	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
	"go.uber.org/zap"
)
func init() {
	caddy.RegisterModule(Handler{})
}
type Handler struct {
	Resources []Resource    `json:"resources,omitempty"`
	Headers   *HeaderConfig `json:"headers,omitempty"`
	logger *zap.Logger
}
func (Handler) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID:  "http.handlers.push",
		New: func() caddy.Module { return new(Handler) },
	}
}
func (h *Handler) Provision(ctx caddy.Context) error {
	h.logger = ctx.Logger(h)
	if h.Headers != nil {
		err := h.Headers.Provision(ctx)
		if err != nil {
			return fmt.Errorf("provisioning header operations: %v", err)
		}
	}
	return nil
}
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
	pusher, ok := w.(http.Pusher)
	if !ok {
		return next.ServeHTTP(w, r)
	}
	if _, ok := r.Header[pushHeader]; ok {
		return next.ServeHTTP(w, r)
	}
	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
	hdr := h.initializePushHeaders(r, repl)
	for _, resource := range h.Resources {
		h.logger.Debug("pushing resource",
			zap.String("uri", r.RequestURI),
			zap.String("push_method", resource.Method),
			zap.String("push_target", resource.Target),
			zap.Object("push_headers", caddyhttp.LoggableHTTPHeader(hdr)))
		err := pusher.Push(repl.ReplaceAll(resource.Target, "."), &http.PushOptions{
			Method: resource.Method,
			Header: hdr,
		})
		if err != nil {
			break
		}
	}
	lp := linkPusher{
		ResponseWriterWrapper: &caddyhttp.ResponseWriterWrapper{ResponseWriter: w},
		handler:               h,
		pusher:                pusher,
		header:                hdr,
		request:               r,
	}
	if err := next.ServeHTTP(lp, r); err != nil {
		return err
	}
	return nil
}
func (h Handler) initializePushHeaders(r *http.Request, repl *caddy.Replacer) http.Header {
	hdr := make(http.Header)
	hdr.Set(pushHeader, "1")
	for _, fieldName := range safeHeaders {
		if vals, ok := r.Header[fieldName]; ok {
			hdr[fieldName] = vals
		}
	}
	if h.Headers != nil {
		h.Headers.ApplyTo(hdr, repl)
	}
	return hdr
}
func (h Handler) servePreloadLinks(pusher http.Pusher, hdr http.Header, resources []string) {
	for _, resource := range resources {
		for _, resource := range parseLinkHeader(resource) {
			if _, ok := resource.params["nopush"]; ok {
				continue
			}
			if isRemoteResource(resource.uri) {
				continue
			}
			err := pusher.Push(resource.uri, &http.PushOptions{
				Header: hdr,
			})
			if err != nil {
				return
			}
		}
	}
}
type Resource struct {
	Method string `json:"method,omitempty"`
	Target string `json:"target,omitempty"`
}
type HeaderConfig struct {
	headers.HeaderOps
}
type linkPusher struct {
	*caddyhttp.ResponseWriterWrapper
	handler Handler
	pusher  http.Pusher
	header  http.Header
	request *http.Request
}
func (lp linkPusher) WriteHeader(statusCode int) {
	if links, ok := lp.ResponseWriter.Header()["Link"]; ok {
		if val := caddyhttp.GetVar(lp.request.Context(), pushedLink); val == nil {
			lp.handler.logger.Debug("pushing Link resources", zap.Strings("linked", links))
			caddyhttp.SetVar(lp.request.Context(), pushedLink, true)
			lp.handler.servePreloadLinks(lp.pusher, lp.header, links)
		}
	}
	lp.ResponseWriter.WriteHeader(statusCode)
}