285 lines
6.2 KiB
Go
285 lines
6.2 KiB
Go
package director
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"sort"
|
|
|
|
"forge.cadoles.com/Cadoles/go-proxy"
|
|
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
|
"forge.cadoles.com/cadoles/bouncer/internal/cache"
|
|
bouncersentry "forge.cadoles.com/cadoles/bouncer/internal/sentry"
|
|
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
|
"github.com/pkg/errors"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"gitlab.com/wpetit/goweb/logger"
|
|
)
|
|
|
|
type Director struct {
|
|
proxyRepository store.ProxyRepository
|
|
layerRepository store.LayerRepository
|
|
layerRegistry *LayerRegistry
|
|
|
|
proxyCache cache.Cache[string, []*store.Proxy]
|
|
layerCache cache.Cache[string, []*store.Layer]
|
|
|
|
handleError HandleErrorFunc
|
|
}
|
|
|
|
func (d *Director) rewriteRequest(r *http.Request) (*http.Request, error) {
|
|
ctx := r.Context()
|
|
|
|
proxies, err := d.getProxies(ctx)
|
|
if err != nil {
|
|
return r, errors.WithStack(err)
|
|
}
|
|
|
|
url := getRequestURL(r)
|
|
ctx = withOriginalURL(ctx, url)
|
|
ctx = logger.With(ctx, logger.F("url", url.String()))
|
|
|
|
layers := make([]*store.Layer, 0)
|
|
|
|
for _, p := range proxies {
|
|
for _, from := range p.From {
|
|
logger.Debug(
|
|
ctx, "matching request with proxy's from",
|
|
logger.F("from", from),
|
|
)
|
|
|
|
if matches := wildcard.Match(url.String(), from); !matches {
|
|
continue
|
|
}
|
|
|
|
logger.Debug(
|
|
ctx, "proxy's from matched",
|
|
logger.F("from", from),
|
|
)
|
|
|
|
ctx = logger.With(ctx,
|
|
logger.F("proxy", p.Name),
|
|
logger.F("host", r.Host),
|
|
logger.F("remoteAddr", r.RemoteAddr),
|
|
)
|
|
|
|
metricProxyRequestsTotal.With(prometheus.Labels{metricLabelProxy: string(p.Name)}).Add(1)
|
|
|
|
proxyLayers, err := d.getLayers(ctx, p.Name)
|
|
if err != nil {
|
|
return r, errors.WithStack(err)
|
|
}
|
|
|
|
layers = append(layers, proxyLayers...)
|
|
|
|
if p.To == "" {
|
|
continue
|
|
}
|
|
|
|
toURL, err := url.Parse(p.To)
|
|
if err != nil {
|
|
return r, errors.WithStack(err)
|
|
}
|
|
|
|
r.URL.Host = toURL.Host
|
|
r.URL.Scheme = toURL.Scheme
|
|
r.URL.Path = toURL.JoinPath(r.URL.Path).Path
|
|
|
|
ctx = withLayers(ctx, layers)
|
|
r = r.WithContext(ctx)
|
|
|
|
return r, nil
|
|
}
|
|
}
|
|
|
|
ctx = withLayers(ctx, layers)
|
|
r = r.WithContext(ctx)
|
|
|
|
return r, nil
|
|
}
|
|
|
|
const proxiesCacheKey = "proxies"
|
|
|
|
func (d *Director) getProxies(ctx context.Context) ([]*store.Proxy, error) {
|
|
proxies, exists := d.proxyCache.Get(proxiesCacheKey)
|
|
if exists {
|
|
return proxies, nil
|
|
}
|
|
|
|
headers, err := d.proxyRepository.QueryProxy(ctx, store.WithProxyQueryEnabled(true))
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
sort.Sort(store.ByProxyWeight(headers))
|
|
|
|
proxies = make([]*store.Proxy, 0, len(headers))
|
|
|
|
for _, h := range headers {
|
|
if !h.Enabled {
|
|
continue
|
|
}
|
|
|
|
proxy, err := d.proxyRepository.GetProxy(ctx, h.Name)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
proxies = append(proxies, proxy)
|
|
}
|
|
|
|
d.proxyCache.Set(proxiesCacheKey, proxies)
|
|
|
|
return proxies, nil
|
|
}
|
|
|
|
func (d *Director) getLayers(ctx context.Context, proxyName store.ProxyName) ([]*store.Layer, error) {
|
|
cacheKey := "layers-" + string(proxyName)
|
|
|
|
layers, exists := d.layerCache.Get(cacheKey)
|
|
if exists {
|
|
return layers, nil
|
|
}
|
|
|
|
headers, err := d.layerRepository.QueryLayers(ctx, proxyName, store.WithLayerQueryEnabled(true))
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
sort.Sort(store.ByLayerWeight(headers))
|
|
|
|
layers = make([]*store.Layer, 0, len(headers))
|
|
|
|
for _, h := range headers {
|
|
if !h.Enabled {
|
|
continue
|
|
}
|
|
|
|
layer, err := d.layerRepository.GetLayer(ctx, proxyName, h.Name)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
layers = append(layers, layer)
|
|
}
|
|
|
|
d.layerCache.Set(cacheKey, layers)
|
|
|
|
return layers, nil
|
|
}
|
|
|
|
func (d *Director) RequestTransformer() proxy.RequestTransformer {
|
|
return func(r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
layers, err := ctxLayers(ctx)
|
|
if err != nil {
|
|
if errors.Is(err, errContextKeyNotFound) {
|
|
return
|
|
}
|
|
|
|
logger.Error(ctx, "could not retrieve layers from context", logger.CapturedE(errors.WithStack(err)))
|
|
|
|
return
|
|
}
|
|
|
|
for _, layer := range layers {
|
|
transformerLayer, ok := d.layerRegistry.GetRequestTransformer(layer.Type)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
transformer := transformerLayer.RequestTransformer(layer)
|
|
|
|
transformer(r)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d *Director) ResponseTransformer() proxy.ResponseTransformer {
|
|
return func(r *http.Response) error {
|
|
ctx := r.Request.Context()
|
|
layers, err := ctxLayers(ctx)
|
|
if err != nil {
|
|
if errors.Is(err, errContextKeyNotFound) {
|
|
return nil
|
|
}
|
|
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
for _, layer := range layers {
|
|
transformerLayer, ok := d.layerRegistry.GetResponseTransformer(layer.Type)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
transformer := transformerLayer.ResponseTransformer(layer)
|
|
|
|
if err := transformer(r); err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (d *Director) Middleware() proxy.Middleware {
|
|
return func(next http.Handler) http.Handler {
|
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
|
ctx := withHandleError(r.Context(), d.handleError)
|
|
r = r.WithContext(ctx)
|
|
|
|
r, err := d.rewriteRequest(r)
|
|
if err != nil {
|
|
HandleError(ctx, w, r, http.StatusInternalServerError, errors.Wrap(err, "could not rewrite request"))
|
|
return
|
|
}
|
|
|
|
ctx = r.Context()
|
|
|
|
layers, err := ctxLayers(ctx)
|
|
if err != nil {
|
|
if errors.Is(err, errContextKeyNotFound) {
|
|
return
|
|
}
|
|
|
|
HandleError(ctx, w, r, http.StatusInternalServerError, errors.Wrap(err, "could not retrieve proxy and layers from context"))
|
|
return
|
|
}
|
|
|
|
httpMiddlewares := make([]proxy.Middleware, 0)
|
|
for _, layer := range layers {
|
|
middleware, ok := d.layerRegistry.GetMiddleware(layer.Type)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
httpMiddlewares = append(httpMiddlewares, middleware.Middleware(layer))
|
|
}
|
|
|
|
handler := createMiddlewareChain(next, httpMiddlewares)
|
|
handler = bouncersentry.SpanHandler(handler, "director.proxy")
|
|
|
|
handler.ServeHTTP(w, r)
|
|
}
|
|
|
|
return http.HandlerFunc(fn)
|
|
}
|
|
}
|
|
|
|
func New(proxyRepository store.ProxyRepository, layerRepository store.LayerRepository, funcs ...OptionFunc) *Director {
|
|
opts := NewOptions(funcs...)
|
|
|
|
registry := NewLayerRegistry(opts.Layers...)
|
|
|
|
return &Director{
|
|
proxyRepository: proxyRepository,
|
|
layerRepository: layerRepository,
|
|
layerRegistry: registry,
|
|
proxyCache: opts.ProxyCache,
|
|
layerCache: opts.LayerCache,
|
|
handleError: opts.HandleError,
|
|
}
|
|
}
|