Extract internal router creation from server
This commit is contained in:
parent
05968eb232
commit
9daae9c705
8 changed files with 697 additions and 254 deletions
132
configuration/router/internal_router.go
Normal file
132
configuration/router/internal_router.go
Normal file
|
@ -0,0 +1,132 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/metrics"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
mauth "github.com/containous/traefik/middlewares/auth"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/urfave/negroni"
|
||||
)
|
||||
|
||||
// NewInternalRouterAggregator Create a new internalRouterAggregator
|
||||
func NewInternalRouterAggregator(globalConfiguration configuration.GlobalConfiguration, entryPointName string) *InternalRouterAggregator {
|
||||
var serverMiddlewares []negroni.Handler
|
||||
|
||||
if globalConfiguration.EntryPoints[entryPointName].WhiteList != nil {
|
||||
ipWhitelistMiddleware, err := middlewares.NewIPWhiteLister(
|
||||
globalConfiguration.EntryPoints[entryPointName].WhiteList.SourceRange,
|
||||
globalConfiguration.EntryPoints[entryPointName].WhiteList.UseXForwardedFor)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating whitelist middleware: %s", err)
|
||||
}
|
||||
if ipWhitelistMiddleware != nil {
|
||||
serverMiddlewares = append(serverMiddlewares, ipWhitelistMiddleware)
|
||||
}
|
||||
}
|
||||
|
||||
if globalConfiguration.EntryPoints[entryPointName].Auth != nil {
|
||||
authMiddleware, err := mauth.NewAuthenticator(globalConfiguration.EntryPoints[entryPointName].Auth, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating authenticator middleware: %s", err)
|
||||
}
|
||||
serverMiddlewares = append(serverMiddlewares, authMiddleware)
|
||||
}
|
||||
|
||||
router := InternalRouterAggregator{}
|
||||
routerWithPrefix := InternalRouterAggregator{}
|
||||
routerWithPrefixAndMiddleware := InternalRouterAggregator{}
|
||||
|
||||
if globalConfiguration.Metrics != nil && globalConfiguration.Metrics.Prometheus != nil && globalConfiguration.Metrics.Prometheus.EntryPoint == entryPointName {
|
||||
routerWithPrefixAndMiddleware.AddRouter(metrics.PrometheusHandler{})
|
||||
}
|
||||
|
||||
if globalConfiguration.Rest != nil && globalConfiguration.Rest.EntryPoint == entryPointName {
|
||||
routerWithPrefixAndMiddleware.AddRouter(globalConfiguration.Rest)
|
||||
}
|
||||
|
||||
if globalConfiguration.API != nil && globalConfiguration.API.EntryPoint == entryPointName {
|
||||
routerWithPrefixAndMiddleware.AddRouter(globalConfiguration.API)
|
||||
}
|
||||
|
||||
if globalConfiguration.Ping != nil && globalConfiguration.Ping.EntryPoint == entryPointName {
|
||||
routerWithPrefix.AddRouter(globalConfiguration.Ping)
|
||||
}
|
||||
|
||||
if globalConfiguration.ACME != nil && globalConfiguration.ACME.HTTPChallenge != nil && globalConfiguration.ACME.HTTPChallenge.EntryPoint == entryPointName {
|
||||
router.AddRouter(globalConfiguration.ACME)
|
||||
}
|
||||
|
||||
realRouterWithMiddleware := WithMiddleware{router: &routerWithPrefixAndMiddleware, routerMiddlewares: serverMiddlewares}
|
||||
if globalConfiguration.Web != nil && globalConfiguration.Web.Path != "" {
|
||||
router.AddRouter(&WithPrefix{PathPrefix: globalConfiguration.Web.Path, Router: &routerWithPrefix})
|
||||
router.AddRouter(&WithPrefix{PathPrefix: globalConfiguration.Web.Path, Router: &realRouterWithMiddleware})
|
||||
} else {
|
||||
router.AddRouter(&routerWithPrefix)
|
||||
router.AddRouter(&realRouterWithMiddleware)
|
||||
}
|
||||
|
||||
return &router
|
||||
}
|
||||
|
||||
// WithMiddleware router with internal middleware
|
||||
type WithMiddleware struct {
|
||||
router types.InternalRouter
|
||||
routerMiddlewares []negroni.Handler
|
||||
}
|
||||
|
||||
// AddRoutes Add routes to the router
|
||||
func (wm *WithMiddleware) AddRoutes(systemRouter *mux.Router) {
|
||||
realRouter := systemRouter.PathPrefix("/").Subrouter()
|
||||
|
||||
wm.router.AddRoutes(realRouter)
|
||||
|
||||
if len(wm.routerMiddlewares) > 0 {
|
||||
realRouter.Walk(wrapRoute(wm.routerMiddlewares))
|
||||
}
|
||||
}
|
||||
|
||||
// WithPrefix router which add a prefix
|
||||
type WithPrefix struct {
|
||||
Router types.InternalRouter
|
||||
PathPrefix string
|
||||
}
|
||||
|
||||
// AddRoutes Add routes to the router
|
||||
func (wp *WithPrefix) AddRoutes(systemRouter *mux.Router) {
|
||||
realRouter := systemRouter.PathPrefix("/").Subrouter()
|
||||
if wp.PathPrefix != "" {
|
||||
realRouter = systemRouter.PathPrefix(wp.PathPrefix).Subrouter()
|
||||
realRouter.StrictSlash(true)
|
||||
realRouter.SkipClean(true)
|
||||
}
|
||||
wp.Router.AddRoutes(realRouter)
|
||||
}
|
||||
|
||||
// InternalRouterAggregator InternalRouter that aggregate other internalRouter
|
||||
type InternalRouterAggregator struct {
|
||||
internalRouters []types.InternalRouter
|
||||
}
|
||||
|
||||
// AddRouter add a router in the aggregator
|
||||
func (r *InternalRouterAggregator) AddRouter(router types.InternalRouter) {
|
||||
r.internalRouters = append(r.internalRouters, router)
|
||||
}
|
||||
|
||||
// AddRoutes Add routes to the router
|
||||
func (r *InternalRouterAggregator) AddRoutes(systemRouter *mux.Router) {
|
||||
for _, router := range r.internalRouters {
|
||||
router.AddRoutes(systemRouter)
|
||||
}
|
||||
}
|
||||
|
||||
// wrapRoute with middlewares
|
||||
func wrapRoute(middlewares []negroni.Handler) func(*mux.Route, *mux.Router, []*mux.Route) error {
|
||||
return func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
|
||||
middles := append(middlewares, negroni.Wrap(route.GetHandler()))
|
||||
route.Handler(negroni.New(middles...))
|
||||
return nil
|
||||
}
|
||||
}
|
346
configuration/router/internal_router_test.go
Normal file
346
configuration/router/internal_router_test.go
Normal file
|
@ -0,0 +1,346 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/api"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/ping"
|
||||
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/negroni"
|
||||
)
|
||||
|
||||
func TestNewInternalRouterAggregatorWithWebPath(t *testing.T) {
|
||||
currentConfiguration := &safe.Safe{}
|
||||
currentConfiguration.Set(types.Configurations{})
|
||||
|
||||
globalConfiguration := configuration.GlobalConfiguration{
|
||||
Web: &configuration.WebCompatibility{
|
||||
Path: "/prefix",
|
||||
},
|
||||
API: &api.Handler{
|
||||
EntryPoint: "traefik",
|
||||
CurrentConfigurations: currentConfiguration,
|
||||
},
|
||||
Ping: &ping.Handler{
|
||||
EntryPoint: "traefik",
|
||||
},
|
||||
ACME: &acme.ACME{
|
||||
HTTPChallenge: &acmeprovider.HTTPChallenge{
|
||||
EntryPoint: "traefik",
|
||||
},
|
||||
},
|
||||
EntryPoints: configuration.EntryPoints{
|
||||
"traefik": &configuration.EntryPoint{},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
testedURL string
|
||||
expectedStatusCode int
|
||||
}{
|
||||
{
|
||||
desc: "Ping without prefix",
|
||||
testedURL: "/ping",
|
||||
expectedStatusCode: 502,
|
||||
},
|
||||
{
|
||||
desc: "Ping with prefix",
|
||||
testedURL: "/prefix/ping",
|
||||
expectedStatusCode: 200,
|
||||
},
|
||||
{
|
||||
desc: "acme without prefix",
|
||||
testedURL: "/.well-known/acme-challenge/token",
|
||||
expectedStatusCode: 404,
|
||||
},
|
||||
{
|
||||
desc: "api without prefix",
|
||||
testedURL: "/api",
|
||||
expectedStatusCode: 502,
|
||||
},
|
||||
{
|
||||
desc: "api with prefix",
|
||||
testedURL: "/prefix/api",
|
||||
expectedStatusCode: 200,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
router := NewInternalRouterAggregator(globalConfiguration, "traefik")
|
||||
|
||||
internalMuxRouter := mux.NewRouter()
|
||||
router.AddRoutes(internalMuxRouter)
|
||||
internalMuxRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
})
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, test.testedURL, nil)
|
||||
internalMuxRouter.ServeHTTP(recorder, request)
|
||||
|
||||
assert.Equal(t, test.expectedStatusCode, recorder.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewInternalRouterAggregatorWithAuth(t *testing.T) {
|
||||
currentConfiguration := &safe.Safe{}
|
||||
currentConfiguration.Set(types.Configurations{})
|
||||
|
||||
globalConfiguration := configuration.GlobalConfiguration{
|
||||
API: &api.Handler{
|
||||
EntryPoint: "traefik",
|
||||
CurrentConfigurations: currentConfiguration,
|
||||
},
|
||||
Ping: &ping.Handler{
|
||||
EntryPoint: "traefik",
|
||||
},
|
||||
ACME: &acme.ACME{
|
||||
HTTPChallenge: &acmeprovider.HTTPChallenge{
|
||||
EntryPoint: "traefik",
|
||||
},
|
||||
},
|
||||
EntryPoints: configuration.EntryPoints{
|
||||
"traefik": &configuration.EntryPoint{
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
Users: types.Users{"test:test"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
testedURL string
|
||||
expectedStatusCode int
|
||||
}{
|
||||
{
|
||||
desc: "Wrong url",
|
||||
testedURL: "/wrong",
|
||||
expectedStatusCode: 502,
|
||||
},
|
||||
{
|
||||
desc: "Ping without auth",
|
||||
testedURL: "/ping",
|
||||
expectedStatusCode: 200,
|
||||
},
|
||||
{
|
||||
desc: "acme without auth",
|
||||
testedURL: "/.well-known/acme-challenge/token",
|
||||
expectedStatusCode: 404,
|
||||
},
|
||||
{
|
||||
desc: "api with auth",
|
||||
testedURL: "/api",
|
||||
expectedStatusCode: 401,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
router := NewInternalRouterAggregator(globalConfiguration, "traefik")
|
||||
|
||||
internalMuxRouter := mux.NewRouter()
|
||||
router.AddRoutes(internalMuxRouter)
|
||||
internalMuxRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
})
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, test.testedURL, nil)
|
||||
internalMuxRouter.ServeHTTP(recorder, request)
|
||||
|
||||
assert.Equal(t, test.expectedStatusCode, recorder.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewInternalRouterAggregatorWithAuthAndPrefix(t *testing.T) {
|
||||
currentConfiguration := &safe.Safe{}
|
||||
currentConfiguration.Set(types.Configurations{})
|
||||
|
||||
globalConfiguration := configuration.GlobalConfiguration{
|
||||
Web: &configuration.WebCompatibility{
|
||||
Path: "/prefix",
|
||||
},
|
||||
API: &api.Handler{
|
||||
EntryPoint: "traefik",
|
||||
CurrentConfigurations: currentConfiguration,
|
||||
},
|
||||
Ping: &ping.Handler{
|
||||
EntryPoint: "traefik",
|
||||
},
|
||||
ACME: &acme.ACME{
|
||||
HTTPChallenge: &acmeprovider.HTTPChallenge{
|
||||
EntryPoint: "traefik",
|
||||
},
|
||||
},
|
||||
EntryPoints: configuration.EntryPoints{
|
||||
"traefik": &configuration.EntryPoint{
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
Users: types.Users{"test:test"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
testedURL string
|
||||
expectedStatusCode int
|
||||
}{
|
||||
{
|
||||
desc: "Ping without prefix",
|
||||
testedURL: "/ping",
|
||||
expectedStatusCode: 502,
|
||||
},
|
||||
{
|
||||
desc: "Ping without auth and with prefix",
|
||||
testedURL: "/prefix/ping",
|
||||
expectedStatusCode: 200,
|
||||
},
|
||||
{
|
||||
desc: "acme without auth and without prefix",
|
||||
testedURL: "/.well-known/acme-challenge/token",
|
||||
expectedStatusCode: 404,
|
||||
},
|
||||
{
|
||||
desc: "api with auth and prefix",
|
||||
testedURL: "/prefix/api",
|
||||
expectedStatusCode: 401,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
router := NewInternalRouterAggregator(globalConfiguration, "traefik")
|
||||
|
||||
internalMuxRouter := mux.NewRouter()
|
||||
router.AddRoutes(internalMuxRouter)
|
||||
internalMuxRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
})
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, test.testedURL, nil)
|
||||
internalMuxRouter.ServeHTTP(recorder, request)
|
||||
|
||||
assert.Equal(t, test.expectedStatusCode, recorder.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type MockInternalRouterFunc func(systemRouter *mux.Router)
|
||||
|
||||
func (m MockInternalRouterFunc) AddRoutes(systemRouter *mux.Router) {
|
||||
m(systemRouter)
|
||||
}
|
||||
|
||||
func TestWithMiddleware(t *testing.T) {
|
||||
router := WithMiddleware{
|
||||
router: MockInternalRouterFunc(func(systemRouter *mux.Router) {
|
||||
systemRouter.Handle("/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("router"))
|
||||
}))
|
||||
}),
|
||||
routerMiddlewares: []negroni.Handler{
|
||||
negroni.HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
rw.Write([]byte("before middleware1|"))
|
||||
next.ServeHTTP(rw, r)
|
||||
rw.Write([]byte("|after middleware1"))
|
||||
|
||||
}),
|
||||
negroni.HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
rw.Write([]byte("before middleware2|"))
|
||||
next.ServeHTTP(rw, r)
|
||||
rw.Write([]byte("|after middleware2"))
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
internalMuxRouter := mux.NewRouter()
|
||||
router.AddRoutes(internalMuxRouter)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
internalMuxRouter.ServeHTTP(recorder, request)
|
||||
|
||||
obtained := string(recorder.Body.Bytes())
|
||||
|
||||
assert.Equal(t, "before middleware1|before middleware2|router|after middleware2|after middleware1", obtained)
|
||||
}
|
||||
|
||||
func TestWithPrefix(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
prefix string
|
||||
testedURL string
|
||||
expectedStatusCode int
|
||||
}{
|
||||
{
|
||||
desc: "No prefix",
|
||||
testedURL: "/test",
|
||||
expectedStatusCode: 200,
|
||||
},
|
||||
{
|
||||
desc: "With prefix and wrong url",
|
||||
prefix: "/prefix",
|
||||
testedURL: "/test",
|
||||
expectedStatusCode: 404,
|
||||
},
|
||||
{
|
||||
desc: "With prefix",
|
||||
prefix: "/prefix",
|
||||
testedURL: "/prefix/test",
|
||||
expectedStatusCode: 200,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
router := WithPrefix{
|
||||
Router: MockInternalRouterFunc(func(systemRouter *mux.Router) {
|
||||
systemRouter.Handle("/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
}),
|
||||
|
||||
PathPrefix: test.prefix,
|
||||
}
|
||||
internalMuxRouter := mux.NewRouter()
|
||||
router.AddRoutes(internalMuxRouter)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, test.testedURL, nil)
|
||||
internalMuxRouter.ServeHTTP(recorder, request)
|
||||
|
||||
assert.Equal(t, test.expectedStatusCode, recorder.Code)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue