Move code to pkg

This commit is contained in:
Ludovic Fernandez 2019-03-15 09:42:03 +01:00 committed by Traefiker Bot
parent bd4c822670
commit f1b085fa36
465 changed files with 656 additions and 680 deletions

View 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
}
}

View 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)
})
}
}

View 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
View 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)
}

View 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)
}
}

View 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
}