mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			176 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			176 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
package proxyutil
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"errors"
 | 
						|
	"log"
 | 
						|
	"net/http"
 | 
						|
	"net/http/httputil"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	glog "github.com/grafana/grafana/pkg/infra/log"
 | 
						|
	"github.com/grafana/grafana/pkg/middleware/requestmeta"
 | 
						|
	"github.com/grafana/grafana/pkg/services/contexthandler"
 | 
						|
)
 | 
						|
 | 
						|
// StatusClientClosedRequest A non-standard status code introduced by nginx
 | 
						|
// for the case when a client closes the connection while nginx is processing
 | 
						|
// the request.
 | 
						|
// https://httpstatus.in/499/
 | 
						|
const StatusClientClosedRequest = 499
 | 
						|
 | 
						|
// ReverseProxyOption reverse proxy option to configure a httputil.ReverseProxy.
 | 
						|
type ReverseProxyOption func(*httputil.ReverseProxy)
 | 
						|
 | 
						|
// NewReverseProxy creates a new httputil.ReverseProxy with sane default configuration.
 | 
						|
func NewReverseProxy(logger glog.Logger, director func(*http.Request), opts ...ReverseProxyOption) *httputil.ReverseProxy {
 | 
						|
	if logger == nil {
 | 
						|
		panic("logger cannot be nil")
 | 
						|
	}
 | 
						|
 | 
						|
	if director == nil {
 | 
						|
		panic("director cannot be nil")
 | 
						|
	}
 | 
						|
 | 
						|
	p := &httputil.ReverseProxy{
 | 
						|
		FlushInterval: time.Millisecond * 200,
 | 
						|
		ErrorHandler:  errorHandler(logger),
 | 
						|
		ErrorLog:      log.New(&logWrapper{logger: logger}, "", 0),
 | 
						|
		Director:      director,
 | 
						|
	}
 | 
						|
 | 
						|
	for _, opt := range opts {
 | 
						|
		opt(p)
 | 
						|
	}
 | 
						|
 | 
						|
	origDirector := p.Director
 | 
						|
	p.Director = wrapDirector(origDirector)
 | 
						|
 | 
						|
	if p.ModifyResponse == nil {
 | 
						|
		// nolint:bodyclose
 | 
						|
		p.ModifyResponse = modifyResponse(logger)
 | 
						|
	} else {
 | 
						|
		modResponse := p.ModifyResponse
 | 
						|
		p.ModifyResponse = func(resp *http.Response) error {
 | 
						|
			if err := modResponse(resp); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			// nolint:bodyclose
 | 
						|
			return modifyResponse(logger)(resp)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// wrapDirector wraps a director and adds additional functionality.
 | 
						|
func wrapDirector(d func(*http.Request)) func(req *http.Request) {
 | 
						|
	return func(req *http.Request) {
 | 
						|
		list := contexthandler.AuthHTTPHeaderListFromContext(req.Context())
 | 
						|
		if list != nil {
 | 
						|
			for _, name := range list.Items {
 | 
						|
				req.Header.Del(name)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		d(req)
 | 
						|
		PrepareProxyRequest(req)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// deletedHeaders lists a number of headers that we don't want to
 | 
						|
// pass-through from the upstream when using a reverse proxy.
 | 
						|
//
 | 
						|
// These are related to the connection between Grafana and the proxy
 | 
						|
// or instructions that would alter how a browser will interact with
 | 
						|
// future requests to Grafana (such as enabling Strict Transport
 | 
						|
// Security)
 | 
						|
var deletedHeaders = []string{
 | 
						|
	"Alt-Svc",
 | 
						|
	"Close",
 | 
						|
	"Server",
 | 
						|
	"Set-Cookie",
 | 
						|
	"Strict-Transport-Security",
 | 
						|
}
 | 
						|
 | 
						|
// modifyResponse enforces certain constraints on http.Response.
 | 
						|
func modifyResponse(logger glog.Logger) func(resp *http.Response) error {
 | 
						|
	return func(resp *http.Response) error {
 | 
						|
		for _, header := range deletedHeaders {
 | 
						|
			resp.Header.Del(header)
 | 
						|
		}
 | 
						|
 | 
						|
		SetProxyResponseHeaders(resp.Header)
 | 
						|
		SetViaHeader(resp.Header, resp.ProtoMajor, resp.ProtoMinor)
 | 
						|
 | 
						|
		requestmeta.WithStatusSource(resp.Request.Context(), resp.StatusCode)
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type timeoutError interface {
 | 
						|
	error
 | 
						|
	Timeout() bool
 | 
						|
}
 | 
						|
 | 
						|
// errorHandler handles any errors happening while proxying a request and enforces
 | 
						|
// certain HTTP status based on the kind of error.
 | 
						|
// If client cancel/close the request we return 499 StatusClientClosedRequest.
 | 
						|
// If timeout happens while communicating with upstream server we return http.StatusGatewayTimeout.
 | 
						|
// If any other error we return http.StatusBadGateway.
 | 
						|
func errorHandler(logger glog.Logger) func(http.ResponseWriter, *http.Request, error) {
 | 
						|
	return func(w http.ResponseWriter, r *http.Request, err error) {
 | 
						|
		ctxLogger := logger.FromContext(r.Context())
 | 
						|
		requestmeta.WithDownstreamStatusSource(r.Context())
 | 
						|
 | 
						|
		if errors.Is(err, context.Canceled) {
 | 
						|
			ctxLogger.Debug("Proxy request cancelled by client")
 | 
						|
			w.WriteHeader(StatusClientClosedRequest)
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		// nolint:errorlint
 | 
						|
		if timeoutErr, ok := err.(timeoutError); ok && timeoutErr.Timeout() {
 | 
						|
			ctxLogger.Error("Proxy request timed out", "err", err)
 | 
						|
			w.WriteHeader(http.StatusGatewayTimeout)
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		ctxLogger.Error("Proxy request failed", "err", err)
 | 
						|
		w.WriteHeader(http.StatusBadGateway)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type logWrapper struct {
 | 
						|
	logger glog.Logger
 | 
						|
}
 | 
						|
 | 
						|
// Write writes log messages as bytes from proxy.
 | 
						|
func (lw *logWrapper) Write(p []byte) (n int, err error) {
 | 
						|
	withoutNewline := strings.TrimSuffix(string(p), "\n")
 | 
						|
	lw.logger.Error("Proxy request error", "error", withoutNewline)
 | 
						|
	return len(p), nil
 | 
						|
}
 | 
						|
 | 
						|
func WithTransport(transport http.RoundTripper) ReverseProxyOption {
 | 
						|
	if transport == nil {
 | 
						|
		panic("transport cannot be nil")
 | 
						|
	}
 | 
						|
 | 
						|
	return ReverseProxyOption(func(rp *httputil.ReverseProxy) {
 | 
						|
		rp.Transport = transport
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func WithModifyResponse(fn func(*http.Response) error) ReverseProxyOption {
 | 
						|
	if fn == nil {
 | 
						|
		panic("fn cannot be nil")
 | 
						|
	}
 | 
						|
 | 
						|
	return ReverseProxyOption(func(rp *httputil.ReverseProxy) {
 | 
						|
		rp.ModifyResponse = fn
 | 
						|
	})
 | 
						|
}
 |