Move code to pkg
This commit is contained in:
parent
bd4c822670
commit
f1b085fa36
465 changed files with 656 additions and 680 deletions
109
pkg/server/router/route_appender_aggregator.go
Normal file
109
pkg/server/router/route_appender_aggregator.go
Normal file
|
@ -0,0 +1,109 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/pkg/api"
|
||||
"github.com/containous/traefik/pkg/config/static"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
"github.com/containous/traefik/pkg/metrics"
|
||||
"github.com/containous/traefik/pkg/safe"
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
)
|
||||
|
||||
// chainBuilder The contract of the middleware builder
|
||||
type chainBuilder interface {
|
||||
BuildChain(ctx context.Context, middlewares []string) *alice.Chain
|
||||
}
|
||||
|
||||
// NewRouteAppenderAggregator Creates a new RouteAppenderAggregator
|
||||
func NewRouteAppenderAggregator(ctx context.Context, chainBuilder chainBuilder, conf static.Configuration, entryPointName string, currentConfiguration *safe.Safe) *RouteAppenderAggregator {
|
||||
aggregator := &RouteAppenderAggregator{}
|
||||
|
||||
if conf.Providers != nil && conf.Providers.Rest != nil {
|
||||
aggregator.AddAppender(conf.Providers.Rest)
|
||||
}
|
||||
|
||||
if conf.API != nil && conf.API.EntryPoint == entryPointName {
|
||||
chain := chainBuilder.BuildChain(ctx, conf.API.Middlewares)
|
||||
aggregator.AddAppender(&WithMiddleware{
|
||||
appender: api.Handler{
|
||||
EntryPoint: conf.API.EntryPoint,
|
||||
Dashboard: conf.API.Dashboard,
|
||||
Statistics: conf.API.Statistics,
|
||||
DashboardAssets: conf.API.DashboardAssets,
|
||||
CurrentConfigurations: currentConfiguration,
|
||||
Debug: conf.Global.Debug,
|
||||
},
|
||||
routerMiddlewares: chain,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
if conf.Ping != nil && conf.Ping.EntryPoint == entryPointName {
|
||||
chain := chainBuilder.BuildChain(ctx, conf.Ping.Middlewares)
|
||||
aggregator.AddAppender(&WithMiddleware{
|
||||
appender: conf.Ping,
|
||||
routerMiddlewares: chain,
|
||||
})
|
||||
}
|
||||
|
||||
if conf.Metrics != nil && conf.Metrics.Prometheus != nil && conf.Metrics.Prometheus.EntryPoint == entryPointName {
|
||||
chain := chainBuilder.BuildChain(ctx, conf.Metrics.Prometheus.Middlewares)
|
||||
aggregator.AddAppender(&WithMiddleware{
|
||||
appender: metrics.PrometheusHandler{},
|
||||
routerMiddlewares: chain,
|
||||
})
|
||||
}
|
||||
|
||||
return aggregator
|
||||
}
|
||||
|
||||
// RouteAppenderAggregator RouteAppender that aggregate other RouteAppender
|
||||
type RouteAppenderAggregator struct {
|
||||
appenders []types.RouteAppender
|
||||
}
|
||||
|
||||
// Append Adds routes to the router
|
||||
func (r *RouteAppenderAggregator) Append(systemRouter *mux.Router) {
|
||||
for _, router := range r.appenders {
|
||||
router.Append(systemRouter)
|
||||
}
|
||||
}
|
||||
|
||||
// AddAppender adds a router in the aggregator
|
||||
func (r *RouteAppenderAggregator) AddAppender(router types.RouteAppender) {
|
||||
r.appenders = append(r.appenders, router)
|
||||
}
|
||||
|
||||
// WithMiddleware router with internal middleware
|
||||
type WithMiddleware struct {
|
||||
appender types.RouteAppender
|
||||
routerMiddlewares *alice.Chain
|
||||
}
|
||||
|
||||
// Append Adds routes to the router
|
||||
func (wm *WithMiddleware) Append(systemRouter *mux.Router) {
|
||||
realRouter := systemRouter.PathPrefix("/").Subrouter()
|
||||
|
||||
wm.appender.Append(realRouter)
|
||||
|
||||
if err := realRouter.Walk(wrapRoute(wm.routerMiddlewares)); err != nil {
|
||||
log.WithoutContext().Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// wrapRoute with middlewares
|
||||
func wrapRoute(middlewares *alice.Chain) func(*mux.Route, *mux.Router, []*mux.Route) error {
|
||||
return func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
|
||||
handler, err := middlewares.Then(route.GetHandler())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
route.Handler(handler)
|
||||
return nil
|
||||
}
|
||||
}
|
114
pkg/server/router/route_appender_aggregator_test.go
Normal file
114
pkg/server/router/route_appender_aggregator_test.go
Normal file
|
@ -0,0 +1,114 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/pkg/config/static"
|
||||
"github.com/containous/traefik/pkg/ping"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type ChainBuilderMock struct {
|
||||
middles map[string]alice.Constructor
|
||||
}
|
||||
|
||||
func (c *ChainBuilderMock) BuildChain(ctx context.Context, middles []string) *alice.Chain {
|
||||
chain := alice.New()
|
||||
|
||||
for _, mName := range middles {
|
||||
if constructor, ok := c.middles[mName]; ok {
|
||||
chain = chain.Append(constructor)
|
||||
}
|
||||
}
|
||||
|
||||
return &chain
|
||||
}
|
||||
|
||||
func TestNewRouteAppenderAggregator(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
staticConf static.Configuration
|
||||
middles map[string]alice.Constructor
|
||||
expected map[string]int
|
||||
}{
|
||||
{
|
||||
desc: "API with auth, ping without auth",
|
||||
staticConf: static.Configuration{
|
||||
Global: &static.Global{},
|
||||
API: &static.API{
|
||||
EntryPoint: "traefik",
|
||||
Middlewares: []string{"dumb"},
|
||||
},
|
||||
Ping: &ping.Handler{
|
||||
EntryPoint: "traefik",
|
||||
},
|
||||
EntryPoints: static.EntryPoints{
|
||||
"traefik": {},
|
||||
},
|
||||
},
|
||||
middles: map[string]alice.Constructor{
|
||||
"dumb": func(_ http.Handler) (http.Handler, error) {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}), nil
|
||||
},
|
||||
},
|
||||
expected: map[string]int{
|
||||
"/wrong": http.StatusBadGateway,
|
||||
"/ping": http.StatusOK,
|
||||
// "/.well-known/acme-challenge/token": http.StatusNotFound, // FIXME
|
||||
"/api/providers": http.StatusUnauthorized,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Wrong entrypoint name",
|
||||
staticConf: static.Configuration{
|
||||
Global: &static.Global{},
|
||||
API: &static.API{
|
||||
EntryPoint: "no",
|
||||
},
|
||||
EntryPoints: static.EntryPoints{
|
||||
"traefik": {},
|
||||
},
|
||||
},
|
||||
expected: map[string]int{
|
||||
"/api/providers": http.StatusBadGateway,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
chainBuilder := &ChainBuilderMock{middles: test.middles}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
router := NewRouteAppenderAggregator(ctx, chainBuilder, test.staticConf, "traefik", nil)
|
||||
|
||||
internalMuxRouter := mux.NewRouter()
|
||||
router.Append(internalMuxRouter)
|
||||
|
||||
internalMuxRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
})
|
||||
|
||||
actual := make(map[string]int)
|
||||
for calledURL := range test.expected {
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, calledURL, nil)
|
||||
internalMuxRouter.ServeHTTP(recorder, request)
|
||||
actual[calledURL] = recorder.Code
|
||||
}
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
38
pkg/server/router/route_appender_factory.go
Normal file
38
pkg/server/router/route_appender_factory.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/static"
|
||||
"github.com/containous/traefik/pkg/provider/acme"
|
||||
"github.com/containous/traefik/pkg/safe"
|
||||
"github.com/containous/traefik/pkg/server/middleware"
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
)
|
||||
|
||||
// NewRouteAppenderFactory Creates a new RouteAppenderFactory
|
||||
func NewRouteAppenderFactory(staticConfiguration static.Configuration, entryPointName string, acmeProvider *acme.Provider) *RouteAppenderFactory {
|
||||
return &RouteAppenderFactory{
|
||||
staticConfiguration: staticConfiguration,
|
||||
entryPointName: entryPointName,
|
||||
acmeProvider: acmeProvider,
|
||||
}
|
||||
}
|
||||
|
||||
// RouteAppenderFactory A factory of RouteAppender
|
||||
type RouteAppenderFactory struct {
|
||||
staticConfiguration static.Configuration
|
||||
entryPointName string
|
||||
acmeProvider *acme.Provider
|
||||
}
|
||||
|
||||
// NewAppender Creates a new RouteAppender
|
||||
func (r *RouteAppenderFactory) NewAppender(ctx context.Context, middlewaresBuilder *middleware.Builder, currentConfiguration *safe.Safe) types.RouteAppender {
|
||||
aggregator := NewRouteAppenderAggregator(ctx, middlewaresBuilder, r.staticConfiguration, r.entryPointName, currentConfiguration)
|
||||
|
||||
if r.acmeProvider != nil && r.acmeProvider.HTTPChallenge != nil && r.acmeProvider.HTTPChallenge.EntryPoint == r.entryPointName {
|
||||
aggregator.AddAppender(r.acmeProvider)
|
||||
}
|
||||
|
||||
return aggregator
|
||||
}
|
195
pkg/server/router/router.go
Normal file
195
pkg/server/router/router.go
Normal file
|
@ -0,0 +1,195 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
"github.com/containous/traefik/pkg/middlewares/accesslog"
|
||||
"github.com/containous/traefik/pkg/middlewares/recovery"
|
||||
"github.com/containous/traefik/pkg/middlewares/tracing"
|
||||
"github.com/containous/traefik/pkg/responsemodifiers"
|
||||
"github.com/containous/traefik/pkg/rules"
|
||||
"github.com/containous/traefik/pkg/server/internal"
|
||||
"github.com/containous/traefik/pkg/server/middleware"
|
||||
"github.com/containous/traefik/pkg/server/service"
|
||||
)
|
||||
|
||||
const (
|
||||
recoveryMiddlewareName = "traefik-internal-recovery"
|
||||
)
|
||||
|
||||
// NewManager Creates a new Manager
|
||||
func NewManager(routers map[string]*config.Router,
|
||||
serviceManager *service.Manager, middlewaresBuilder *middleware.Builder, modifierBuilder *responsemodifiers.Builder,
|
||||
) *Manager {
|
||||
return &Manager{
|
||||
routerHandlers: make(map[string]http.Handler),
|
||||
configs: routers,
|
||||
serviceManager: serviceManager,
|
||||
middlewaresBuilder: middlewaresBuilder,
|
||||
modifierBuilder: modifierBuilder,
|
||||
}
|
||||
}
|
||||
|
||||
// Manager A route/router manager
|
||||
type Manager struct {
|
||||
routerHandlers map[string]http.Handler
|
||||
configs map[string]*config.Router
|
||||
serviceManager *service.Manager
|
||||
middlewaresBuilder *middleware.Builder
|
||||
modifierBuilder *responsemodifiers.Builder
|
||||
}
|
||||
|
||||
// BuildHandlers Builds handler for all entry points
|
||||
func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, tls bool) map[string]http.Handler {
|
||||
entryPointsRouters := m.filteredRouters(rootCtx, entryPoints, tls)
|
||||
|
||||
entryPointHandlers := make(map[string]http.Handler)
|
||||
for entryPointName, routers := range entryPointsRouters {
|
||||
entryPointName := entryPointName
|
||||
ctx := log.With(rootCtx, log.Str(log.EntryPointName, entryPointName))
|
||||
|
||||
handler, err := m.buildEntryPointHandler(ctx, routers)
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) {
|
||||
return accesslog.NewFieldHandler(next, log.EntryPointName, entryPointName, accesslog.AddOriginFields), nil
|
||||
}).Then(handler)
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Error(err)
|
||||
entryPointHandlers[entryPointName] = handler
|
||||
} else {
|
||||
entryPointHandlers[entryPointName] = handlerWithAccessLog
|
||||
}
|
||||
}
|
||||
|
||||
m.serviceManager.LaunchHealthCheck()
|
||||
|
||||
return entryPointHandlers
|
||||
}
|
||||
|
||||
func contains(entryPoints []string, entryPointName string) bool {
|
||||
for _, name := range entryPoints {
|
||||
if name == entryPointName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string, tls bool) map[string]map[string]*config.Router {
|
||||
entryPointsRouters := make(map[string]map[string]*config.Router)
|
||||
|
||||
for rtName, rt := range m.configs {
|
||||
if (tls && rt.TLS == nil) || (!tls && rt.TLS != nil) {
|
||||
continue
|
||||
}
|
||||
|
||||
eps := rt.EntryPoints
|
||||
if len(eps) == 0 {
|
||||
eps = entryPoints
|
||||
}
|
||||
for _, entryPointName := range eps {
|
||||
if !contains(entryPoints, entryPointName) {
|
||||
log.FromContext(log.With(ctx, log.Str(log.EntryPointName, entryPointName))).
|
||||
Errorf("entryPoint %q doesn't exist", entryPointName)
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := entryPointsRouters[entryPointName]; !ok {
|
||||
entryPointsRouters[entryPointName] = make(map[string]*config.Router)
|
||||
}
|
||||
|
||||
entryPointsRouters[entryPointName][rtName] = rt
|
||||
}
|
||||
}
|
||||
|
||||
return entryPointsRouters
|
||||
}
|
||||
|
||||
func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*config.Router) (http.Handler, error) {
|
||||
router, err := rules.NewRouter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for routerName, routerConfig := range configs {
|
||||
ctxRouter := log.With(ctx, log.Str(log.RouterName, routerName))
|
||||
logger := log.FromContext(ctxRouter)
|
||||
|
||||
ctxRouter = internal.AddProviderInContext(ctxRouter, routerName)
|
||||
|
||||
handler, err := m.buildRouterHandler(ctxRouter, routerName)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = router.AddRoute(routerConfig.Rule, routerConfig.Priority, handler)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
router.SortRoutes()
|
||||
|
||||
chain := alice.New()
|
||||
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
||||
return recovery.New(ctx, next, recoveryMiddlewareName)
|
||||
})
|
||||
|
||||
return chain.Then(router)
|
||||
}
|
||||
|
||||
func (m *Manager) buildRouterHandler(ctx context.Context, routerName string) (http.Handler, error) {
|
||||
if handler, ok := m.routerHandlers[routerName]; ok {
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
configRouter, ok := m.configs[routerName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no configuration for %s", routerName)
|
||||
}
|
||||
|
||||
handler, err := m.buildHTTPHandler(ctx, configRouter, routerName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) {
|
||||
return accesslog.NewFieldHandler(next, accesslog.RouterName, routerName, nil), nil
|
||||
}).Then(handler)
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Error(err)
|
||||
m.routerHandlers[routerName] = handler
|
||||
} else {
|
||||
m.routerHandlers[routerName] = handlerWithAccessLog
|
||||
}
|
||||
|
||||
return m.routerHandlers[routerName], nil
|
||||
}
|
||||
|
||||
func (m *Manager) buildHTTPHandler(ctx context.Context, router *config.Router, routerName string) (http.Handler, error) {
|
||||
rm := m.modifierBuilder.Build(ctx, router.Middlewares)
|
||||
|
||||
sHandler, err := m.serviceManager.BuildHTTP(ctx, router.Service, rm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mHandler := m.middlewaresBuilder.BuildChain(ctx, router.Middlewares)
|
||||
|
||||
tHandler := func(next http.Handler) (http.Handler, error) {
|
||||
return tracing.NewForwarder(ctx, routerName, router.Service, next), nil
|
||||
}
|
||||
|
||||
return alice.New().Extend(*mHandler).Append(tHandler).Then(sHandler)
|
||||
}
|
531
pkg/server/router/router_test.go
Normal file
531
pkg/server/router/router_test.go
Normal file
|
@ -0,0 +1,531 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/middlewares/accesslog"
|
||||
"github.com/containous/traefik/pkg/middlewares/requestdecorator"
|
||||
"github.com/containous/traefik/pkg/responsemodifiers"
|
||||
"github.com/containous/traefik/pkg/server/middleware"
|
||||
"github.com/containous/traefik/pkg/server/service"
|
||||
"github.com/containous/traefik/pkg/testhelpers"
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRouterManager_Get(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
|
||||
type ExpectedResult struct {
|
||||
StatusCode int
|
||||
RequestHeaders map[string]string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
routersConfig map[string]*config.Router
|
||||
serviceConfig map[string]*config.Service
|
||||
middlewaresConfig map[string]*config.Middleware
|
||||
entryPoints []string
|
||||
expected ExpectedResult
|
||||
}{
|
||||
{
|
||||
desc: "no middleware",
|
||||
routersConfig: map[string]*config.Router{
|
||||
"foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &config.LoadBalancerService{
|
||||
Servers: []config.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
Method: "wrr",
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoints: []string{"web"},
|
||||
expected: ExpectedResult{StatusCode: http.StatusOK},
|
||||
},
|
||||
{
|
||||
desc: "no load balancer",
|
||||
routersConfig: map[string]*config.Router{
|
||||
"foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
"foo-service": {},
|
||||
},
|
||||
entryPoints: []string{"web"},
|
||||
expected: ExpectedResult{StatusCode: http.StatusNotFound},
|
||||
},
|
||||
{
|
||||
desc: "no middleware, default entry point",
|
||||
routersConfig: map[string]*config.Router{
|
||||
"foo": {
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &config.LoadBalancerService{
|
||||
Servers: []config.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
Method: "wrr",
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoints: []string{"web"},
|
||||
expected: ExpectedResult{StatusCode: http.StatusOK},
|
||||
},
|
||||
{
|
||||
desc: "no middleware, no matching",
|
||||
routersConfig: map[string]*config.Router{
|
||||
"foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`bar.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &config.LoadBalancerService{
|
||||
Servers: []config.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
Method: "wrr",
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoints: []string{"web"},
|
||||
expected: ExpectedResult{StatusCode: http.StatusNotFound},
|
||||
},
|
||||
{
|
||||
desc: "middleware: headers > auth",
|
||||
routersConfig: map[string]*config.Router{
|
||||
"foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Middlewares: []string{"headers-middle", "auth-middle"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &config.LoadBalancerService{
|
||||
Servers: []config.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
Method: "wrr",
|
||||
},
|
||||
},
|
||||
},
|
||||
middlewaresConfig: map[string]*config.Middleware{
|
||||
"auth-middle": {
|
||||
BasicAuth: &config.BasicAuth{
|
||||
Users: []string{"toto:titi"},
|
||||
},
|
||||
},
|
||||
"headers-middle": {
|
||||
Headers: &config.Headers{
|
||||
CustomRequestHeaders: map[string]string{"X-Apero": "beer"},
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoints: []string{"web"},
|
||||
expected: ExpectedResult{
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
RequestHeaders: map[string]string{
|
||||
"X-Apero": "beer",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "middleware: auth > header",
|
||||
routersConfig: map[string]*config.Router{
|
||||
"foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Middlewares: []string{"auth-middle", "headers-middle"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &config.LoadBalancerService{
|
||||
Servers: []config.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
Method: "wrr",
|
||||
},
|
||||
},
|
||||
},
|
||||
middlewaresConfig: map[string]*config.Middleware{
|
||||
"auth-middle": {
|
||||
BasicAuth: &config.BasicAuth{
|
||||
Users: []string{"toto:titi"},
|
||||
},
|
||||
},
|
||||
"headers-middle": {
|
||||
Headers: &config.Headers{
|
||||
CustomRequestHeaders: map[string]string{"X-Apero": "beer"},
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoints: []string{"web"},
|
||||
expected: ExpectedResult{
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
RequestHeaders: map[string]string{
|
||||
"X-Apero": "",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "no middleware with provider name",
|
||||
routersConfig: map[string]*config.Router{
|
||||
"provider-1.foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
"provider-1.foo-service": {
|
||||
LoadBalancer: &config.LoadBalancerService{
|
||||
Servers: []config.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
Method: "wrr",
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoints: []string{"web"},
|
||||
expected: ExpectedResult{StatusCode: http.StatusOK},
|
||||
},
|
||||
{
|
||||
desc: "no middleware with specified provider name",
|
||||
routersConfig: map[string]*config.Router{
|
||||
"provider-1.foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "provider-2.foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
"provider-2.foo-service": {
|
||||
LoadBalancer: &config.LoadBalancerService{
|
||||
Servers: []config.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
Method: "wrr",
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoints: []string{"web"},
|
||||
expected: ExpectedResult{StatusCode: http.StatusOK},
|
||||
},
|
||||
{
|
||||
desc: "middleware: chain with provider name",
|
||||
routersConfig: map[string]*config.Router{
|
||||
"provider-1.foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Middlewares: []string{"provider-2.chain-middle", "headers-middle"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
"provider-1.foo-service": {
|
||||
LoadBalancer: &config.LoadBalancerService{
|
||||
Servers: []config.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
Method: "wrr",
|
||||
},
|
||||
},
|
||||
},
|
||||
middlewaresConfig: map[string]*config.Middleware{
|
||||
"provider-2.chain-middle": {
|
||||
Chain: &config.Chain{Middlewares: []string{"auth-middle"}},
|
||||
},
|
||||
"provider-2.auth-middle": {
|
||||
BasicAuth: &config.BasicAuth{
|
||||
Users: []string{"toto:titi"},
|
||||
},
|
||||
},
|
||||
"provider-1.headers-middle": {
|
||||
Headers: &config.Headers{
|
||||
CustomRequestHeaders: map[string]string{"X-Apero": "beer"},
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoints: []string{"web"},
|
||||
expected: ExpectedResult{
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
RequestHeaders: map[string]string{
|
||||
"X-Apero": "",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
serviceManager := service.NewManager(test.serviceConfig, http.DefaultTransport)
|
||||
middlewaresBuilder := middleware.NewBuilder(test.middlewaresConfig, serviceManager)
|
||||
responseModifierFactory := responsemodifiers.NewBuilder(test.middlewaresConfig)
|
||||
|
||||
routerManager := NewManager(test.routersConfig, serviceManager, middlewaresBuilder, responseModifierFactory)
|
||||
|
||||
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil)
|
||||
|
||||
reqHost := requestdecorator.New(nil)
|
||||
reqHost.ServeHTTP(w, req, handlers["web"].ServeHTTP)
|
||||
|
||||
assert.Equal(t, test.expected.StatusCode, w.Code)
|
||||
|
||||
for key, value := range test.expected.RequestHeaders {
|
||||
assert.Equal(t, value, req.Header.Get(key))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccessLog(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
routersConfig map[string]*config.Router
|
||||
serviceConfig map[string]*config.Service
|
||||
middlewaresConfig map[string]*config.Middleware
|
||||
entryPoints []string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "apply routerName in accesslog (first match)",
|
||||
routersConfig: map[string]*config.Router{
|
||||
"foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
"bar": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`bar.foo`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &config.LoadBalancerService{
|
||||
Servers: []config.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
Method: "wrr",
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoints: []string{"web"},
|
||||
expected: "foo",
|
||||
},
|
||||
{
|
||||
desc: "apply routerName in accesslog (second match)",
|
||||
routersConfig: map[string]*config.Router{
|
||||
"foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`bar.foo`)",
|
||||
},
|
||||
"bar": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*config.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &config.LoadBalancerService{
|
||||
Servers: []config.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
Method: "wrr",
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoints: []string{"web"},
|
||||
expected: "bar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
|
||||
serviceManager := service.NewManager(test.serviceConfig, http.DefaultTransport)
|
||||
middlewaresBuilder := middleware.NewBuilder(test.middlewaresConfig, serviceManager)
|
||||
responseModifierFactory := responsemodifiers.NewBuilder(test.middlewaresConfig)
|
||||
|
||||
routerManager := NewManager(test.routersConfig, serviceManager, middlewaresBuilder, responseModifierFactory)
|
||||
|
||||
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil)
|
||||
|
||||
accesslogger, err := accesslog.NewHandler(&types.AccessLog{
|
||||
Format: "json",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
reqHost := requestdecorator.New(nil)
|
||||
|
||||
accesslogger.ServeHTTP(w, req, http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
reqHost.ServeHTTP(w, req, handlers["web"].ServeHTTP)
|
||||
|
||||
data := accesslog.GetLogData(req)
|
||||
require.NotNil(t, data)
|
||||
|
||||
assert.Equal(t, test.expected, data.Core[accesslog.RouterName])
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type staticTransport struct {
|
||||
res *http.Response
|
||||
}
|
||||
|
||||
func (t *staticTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
return t.res, nil
|
||||
}
|
||||
|
||||
func BenchmarkRouterServe(b *testing.B) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
|
||||
res := &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: ioutil.NopCloser(strings.NewReader("")),
|
||||
}
|
||||
routersConfig := map[string]*config.Router{
|
||||
"foo": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`) && Path(`/`)",
|
||||
},
|
||||
}
|
||||
serviceConfig := map[string]*config.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &config.LoadBalancerService{
|
||||
Servers: []config.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
Method: "wrr",
|
||||
},
|
||||
},
|
||||
}
|
||||
entryPoints := []string{"web"}
|
||||
|
||||
serviceManager := service.NewManager(serviceConfig, &staticTransport{res})
|
||||
middlewaresBuilder := middleware.NewBuilder(map[string]*config.Middleware{}, serviceManager)
|
||||
responseModifierFactory := responsemodifiers.NewBuilder(map[string]*config.Middleware{})
|
||||
|
||||
routerManager := NewManager(routersConfig, serviceManager, middlewaresBuilder, responseModifierFactory)
|
||||
|
||||
handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil)
|
||||
|
||||
reqHost := requestdecorator.New(nil)
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
reqHost.ServeHTTP(w, req, handlers["web"].ServeHTTP)
|
||||
}
|
||||
|
||||
}
|
||||
func BenchmarkService(b *testing.B) {
|
||||
res := &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: ioutil.NopCloser(strings.NewReader("")),
|
||||
}
|
||||
|
||||
serviceConfig := map[string]*config.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &config.LoadBalancerService{
|
||||
Servers: []config.Server{
|
||||
{
|
||||
URL: "tchouck",
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
Method: "wrr",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
serviceManager := service.NewManager(serviceConfig, &staticTransport{res})
|
||||
w := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil)
|
||||
|
||||
handler, _ := serviceManager.BuildHTTP(context.Background(), "foo-service", nil)
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
handler.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
}
|
139
pkg/server/router/tcp/router.go
Normal file
139
pkg/server/router/tcp/router.go
Normal file
|
@ -0,0 +1,139 @@
|
|||
package tcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
"github.com/containous/traefik/pkg/rules"
|
||||
"github.com/containous/traefik/pkg/server/internal"
|
||||
tcpservice "github.com/containous/traefik/pkg/server/service/tcp"
|
||||
"github.com/containous/traefik/pkg/tcp"
|
||||
)
|
||||
|
||||
// NewManager Creates a new Manager
|
||||
func NewManager(routers map[string]*config.TCPRouter,
|
||||
serviceManager *tcpservice.Manager,
|
||||
httpHandlers map[string]http.Handler,
|
||||
httpsHandlers map[string]http.Handler,
|
||||
tlsConfig *tls.Config,
|
||||
) *Manager {
|
||||
return &Manager{
|
||||
configs: routers,
|
||||
serviceManager: serviceManager,
|
||||
httpHandlers: httpHandlers,
|
||||
httpsHandlers: httpsHandlers,
|
||||
tlsConfig: tlsConfig,
|
||||
}
|
||||
}
|
||||
|
||||
// Manager is a route/router manager
|
||||
type Manager struct {
|
||||
configs map[string]*config.TCPRouter
|
||||
serviceManager *tcpservice.Manager
|
||||
httpHandlers map[string]http.Handler
|
||||
httpsHandlers map[string]http.Handler
|
||||
tlsConfig *tls.Config
|
||||
}
|
||||
|
||||
// BuildHandlers builds the handlers for the given entrypoints
|
||||
func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) map[string]*tcp.Router {
|
||||
entryPointsRouters := m.filteredRouters(rootCtx, entryPoints)
|
||||
|
||||
entryPointHandlers := make(map[string]*tcp.Router)
|
||||
for _, entryPointName := range entryPoints {
|
||||
entryPointName := entryPointName
|
||||
|
||||
routers := entryPointsRouters[entryPointName]
|
||||
|
||||
ctx := log.With(rootCtx, log.Str(log.EntryPointName, entryPointName))
|
||||
|
||||
handler, err := m.buildEntryPointHandler(ctx, routers, m.httpHandlers[entryPointName], m.httpsHandlers[entryPointName])
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Error(err)
|
||||
continue
|
||||
}
|
||||
entryPointHandlers[entryPointName] = handler
|
||||
}
|
||||
return entryPointHandlers
|
||||
}
|
||||
|
||||
func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*config.TCPRouter, handlerHTTP http.Handler, handlerHTTPS http.Handler) (*tcp.Router, error) {
|
||||
router := &tcp.Router{}
|
||||
|
||||
router.HTTPHandler(handlerHTTP)
|
||||
router.HTTPSHandler(handlerHTTPS, m.tlsConfig)
|
||||
for routerName, routerConfig := range configs {
|
||||
ctxRouter := log.With(ctx, log.Str(log.RouterName, routerName))
|
||||
logger := log.FromContext(ctxRouter)
|
||||
|
||||
ctxRouter = internal.AddProviderInContext(ctxRouter, routerName)
|
||||
|
||||
handler, err := m.serviceManager.BuildTCP(ctxRouter, routerConfig.Service)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
domains, err := rules.ParseHostSNI(routerConfig.Rule)
|
||||
if err != nil {
|
||||
log.WithoutContext().Debugf("Unknown rule %s", routerConfig.Rule)
|
||||
continue
|
||||
}
|
||||
for _, domain := range domains {
|
||||
log.WithoutContext().Debugf("Add route %s on TCP", domain)
|
||||
switch {
|
||||
case routerConfig.TLS != nil:
|
||||
if routerConfig.TLS.Passthrough {
|
||||
router.AddRoute(domain, handler)
|
||||
} else {
|
||||
router.AddRouteTLS(domain, handler, m.tlsConfig)
|
||||
|
||||
}
|
||||
case domain == "*":
|
||||
router.AddCatchAllNoTLS(handler)
|
||||
default:
|
||||
logger.Warn("TCP Router ignored, cannot specify a Host rule without TLS")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return router, nil
|
||||
}
|
||||
|
||||
func contains(entryPoints []string, entryPointName string) bool {
|
||||
for _, name := range entryPoints {
|
||||
if name == entryPointName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string) map[string]map[string]*config.TCPRouter {
|
||||
entryPointsRouters := make(map[string]map[string]*config.TCPRouter)
|
||||
|
||||
for rtName, rt := range m.configs {
|
||||
eps := rt.EntryPoints
|
||||
if len(eps) == 0 {
|
||||
eps = entryPoints
|
||||
}
|
||||
for _, entryPointName := range eps {
|
||||
if !contains(entryPoints, entryPointName) {
|
||||
log.FromContext(log.With(ctx, log.Str(log.EntryPointName, entryPointName))).
|
||||
Errorf("entryPoint %q doesn't exist", entryPointName)
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := entryPointsRouters[entryPointName]; !ok {
|
||||
entryPointsRouters[entryPointName] = make(map[string]*config.TCPRouter)
|
||||
}
|
||||
|
||||
entryPointsRouters[entryPointName][rtName] = rt
|
||||
}
|
||||
}
|
||||
|
||||
return entryPointsRouters
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue