74 lines
2.0 KiB
Go
74 lines
2.0 KiB
Go
package feature
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// HTTPProxy is an HTTP proxy that's guided by a feature flag. If the feature flag
|
|
// presented to it is enabled, it will perform the proxying behavior. Otherwise
|
|
// it will be a no-op.
|
|
type HTTPProxy struct {
|
|
proxy *httputil.ReverseProxy
|
|
logger *zap.Logger
|
|
enabler ProxyEnabler
|
|
}
|
|
|
|
// NewHTTPProxy returns a new Proxy.
|
|
func NewHTTPProxy(dest *url.URL, logger *zap.Logger, enabler ProxyEnabler) *HTTPProxy {
|
|
return &HTTPProxy{
|
|
proxy: newReverseProxy(dest, enabler.Key()),
|
|
logger: logger,
|
|
enabler: enabler,
|
|
}
|
|
}
|
|
|
|
// Do performs the proxying. It returns whether or not the request was proxied.
|
|
func (p *HTTPProxy) Do(w http.ResponseWriter, r *http.Request) bool {
|
|
if p.enabler.Enabled(r.Context()) {
|
|
p.proxy.ServeHTTP(w, r)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
const (
|
|
// headerProxyFlag is the HTTP header for enriching the request and response
|
|
// with the feature flag key that precipitated the proxying behavior.
|
|
headerProxyFlag = "X-Platform-Proxy-Flag"
|
|
)
|
|
|
|
// newReverseProxy creates a new single-host reverse proxy.
|
|
func newReverseProxy(dest *url.URL, enablerKey string) *httputil.ReverseProxy {
|
|
proxy := httputil.NewSingleHostReverseProxy(dest)
|
|
|
|
defaultDirector := proxy.Director
|
|
proxy.Director = func(r *http.Request) {
|
|
defaultDirector(r)
|
|
|
|
r.Header.Set(headerProxyFlag, enablerKey)
|
|
|
|
// Override r.Host to prevent us sending this request back to ourselves.
|
|
// A bug in the stdlib causes this value to be preferred over the
|
|
// r.URL.Host (which is set in the default Director) if r.Host isn't
|
|
// empty (which it isn't).
|
|
// https://github.com/golang/go/issues/28168
|
|
r.Host = dest.Host
|
|
}
|
|
proxy.ModifyResponse = func(r *http.Response) error {
|
|
r.Header.Set(headerProxyFlag, enablerKey)
|
|
return nil
|
|
}
|
|
return proxy
|
|
}
|
|
|
|
// ProxyEnabler is a boolean feature flag.
|
|
type ProxyEnabler interface {
|
|
Key() string
|
|
Enabled(ctx context.Context, fs ...Flagger) bool
|
|
}
|