SchemeRedirect Middleware
Co-authored-by: jbdoumenjou <jb.doumenjou@gmail.com>
This commit is contained in:
parent
04958c6951
commit
a433e469cc
11 changed files with 407 additions and 66 deletions
|
@ -20,11 +20,6 @@ func Test_doOnJSON(t *testing.T) {
|
||||||
"Network": "",
|
"Network": "",
|
||||||
"Address": ":80",
|
"Address": ":80",
|
||||||
"TLS": null,
|
"TLS": null,
|
||||||
"Redirect": {
|
|
||||||
"EntryPoint": "https",
|
|
||||||
"Regex": "",
|
|
||||||
"Replacement": ""
|
|
||||||
},
|
|
||||||
"Auth": null,
|
"Auth": null,
|
||||||
"Compress": false
|
"Compress": false
|
||||||
},
|
},
|
||||||
|
@ -36,7 +31,6 @@ func Test_doOnJSON(t *testing.T) {
|
||||||
"Certificates": null,
|
"Certificates": null,
|
||||||
"ClientCAFiles": null
|
"ClientCAFiles": null
|
||||||
},
|
},
|
||||||
"Redirect": null,
|
|
||||||
"Auth": null,
|
"Auth": null,
|
||||||
"Compress": false
|
"Compress": false
|
||||||
}
|
}
|
||||||
|
@ -109,11 +103,6 @@ func Test_doOnJSON(t *testing.T) {
|
||||||
"Network": "",
|
"Network": "",
|
||||||
"Address": ":80",
|
"Address": ":80",
|
||||||
"TLS": null,
|
"TLS": null,
|
||||||
"Redirect": {
|
|
||||||
"EntryPoint": "https",
|
|
||||||
"Regex": "",
|
|
||||||
"Replacement": ""
|
|
||||||
},
|
|
||||||
"Auth": null,
|
"Auth": null,
|
||||||
"Compress": false
|
"Compress": false
|
||||||
},
|
},
|
||||||
|
@ -125,7 +114,6 @@ func Test_doOnJSON(t *testing.T) {
|
||||||
"Certificates": null,
|
"Certificates": null,
|
||||||
"ClientCAFiles": null
|
"ClientCAFiles": null
|
||||||
},
|
},
|
||||||
"Redirect": null,
|
|
||||||
"Auth": null,
|
"Auth": null,
|
||||||
"Compress": false
|
"Compress": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,8 @@ type Middleware struct {
|
||||||
Headers *Headers `json:"headers,omitempty"`
|
Headers *Headers `json:"headers,omitempty"`
|
||||||
Errors *ErrorPage `json:"errors,omitempty"`
|
Errors *ErrorPage `json:"errors,omitempty"`
|
||||||
RateLimit *RateLimit `json:"rateLimit,omitempty"`
|
RateLimit *RateLimit `json:"rateLimit,omitempty"`
|
||||||
Redirect *Redirect `json:"redirect,omitempty"`
|
RedirectRegex *RedirectRegex `json:"redirectregex,omitempty"`
|
||||||
|
RedirectScheme *RedirectScheme `json:"redirectscheme,omitempty"`
|
||||||
BasicAuth *BasicAuth `json:"basicAuth,omitempty"`
|
BasicAuth *BasicAuth `json:"basicAuth,omitempty"`
|
||||||
DigestAuth *DigestAuth `json:"digestAuth,omitempty"`
|
DigestAuth *DigestAuth `json:"digestAuth,omitempty"`
|
||||||
ForwardAuth *ForwardAuth `json:"forwardAuth,omitempty"`
|
ForwardAuth *ForwardAuth `json:"forwardAuth,omitempty"`
|
||||||
|
@ -229,13 +230,20 @@ func (r *RateLimit) SetDefaults() {
|
||||||
r.ExtractorFunc = "request.host"
|
r.ExtractorFunc = "request.host"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect holds the redirection configuration of an entry point to another, or to an URL.
|
// RedirectRegex holds the redirection configuration.
|
||||||
type Redirect struct {
|
type RedirectRegex struct {
|
||||||
Regex string `json:"regex,omitempty"`
|
Regex string `json:"regex,omitempty"`
|
||||||
Replacement string `json:"replacement,omitempty"`
|
Replacement string `json:"replacement,omitempty"`
|
||||||
Permanent bool `json:"permanent,omitempty"`
|
Permanent bool `json:"permanent,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RedirectScheme holds the scheme redirection configuration.
|
||||||
|
type RedirectScheme struct {
|
||||||
|
Scheme string `json:"scheme,omitempty"`
|
||||||
|
Port string `json:"port,omitempty"`
|
||||||
|
Permanent bool `json:"permanent,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// ReplacePath holds the ReplacePath configuration.
|
// ReplacePath holds the ReplacePath configuration.
|
||||||
type ReplacePath struct {
|
type ReplacePath struct {
|
||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
|
@ -247,7 +255,7 @@ type ReplacePathRegex struct {
|
||||||
Replacement string `json:"replacement,omitempty"`
|
Replacement string `json:"replacement,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retry contains request retry config
|
// Retry holds the retry configuration.
|
||||||
type Retry struct {
|
type Retry struct {
|
||||||
Attempts int `description:"Number of attempts" export:"true"`
|
Attempts int `description:"Number of attempts" export:"true"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,6 @@ logLevel = "DEBUG"
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":8888"
|
address = ":8888"
|
||||||
|
|
||||||
[entryPoints.http.redirect]
|
|
||||||
entryPoint = "https"
|
|
||||||
|
|
||||||
[entryPoints.https]
|
[entryPoints.https]
|
||||||
address = ":8443"
|
address = ":8443"
|
||||||
[entryPoints.https.tls]
|
[entryPoints.https.tls]
|
||||||
|
@ -92,9 +89,9 @@ logLevel = "DEBUG"
|
||||||
path = "/api"
|
path = "/api"
|
||||||
[Middlewares.api-slash-replace-path.ReplacePath]
|
[Middlewares.api-slash-replace-path.ReplacePath]
|
||||||
path = "/api/"
|
path = "/api/"
|
||||||
[Middlewares.redirect-https.redirect]
|
[Middlewares.redirect-https.redirectScheme]
|
||||||
regex = "^(?:https?://)?([\\w\\._-]+)(?::\\d+)?(.*)$"
|
scheme = "https"
|
||||||
replacement = "https://${1}:8443${2}"
|
port = "8443"
|
||||||
|
|
||||||
[Services]
|
[Services]
|
||||||
[Services.service1]
|
[Services.service1]
|
||||||
|
|
|
@ -53,8 +53,8 @@ frontendRedirect:
|
||||||
- traefik.routers.rt-frontendRedirect.entryPoints=frontendRedirect
|
- traefik.routers.rt-frontendRedirect.entryPoints=frontendRedirect
|
||||||
- traefik.routers.rt-frontendRedirect.rule=Path:/test
|
- traefik.routers.rt-frontendRedirect.rule=Path:/test
|
||||||
- traefik.routers.rt-frontendRedirect.middlewares=redirecthttp
|
- traefik.routers.rt-frontendRedirect.middlewares=redirecthttp
|
||||||
- traefik.middlewares.redirecthttp.redirect.regex=^(?:https?://)?([\w\._-]+)(?::\d+)?(.*)$$
|
- traefik.middlewares.redirecthttp.redirectScheme.scheme=http
|
||||||
- traefik.middlewares.redirecthttp.redirect.replacement=http://$${1}:8000$${2}
|
- traefik.middlewares.redirecthttp.redirectScheme.port=8000
|
||||||
- traefik.services.service3.loadbalancer.server.port=80
|
- traefik.services.service3.loadbalancer.server.port=80
|
||||||
rateLimit:
|
rateLimit:
|
||||||
image: containous/whoami
|
image: containous/whoami
|
||||||
|
|
|
@ -10,17 +10,11 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containous/traefik/config"
|
|
||||||
"github.com/containous/traefik/middlewares"
|
|
||||||
"github.com/containous/traefik/tracing"
|
"github.com/containous/traefik/tracing"
|
||||||
"github.com/opentracing/opentracing-go/ext"
|
"github.com/opentracing/opentracing-go/ext"
|
||||||
"github.com/vulcand/oxy/utils"
|
"github.com/vulcand/oxy/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
typeName = "Redirect"
|
|
||||||
)
|
|
||||||
|
|
||||||
type redirect struct {
|
type redirect struct {
|
||||||
next http.Handler
|
next http.Handler
|
||||||
regex *regexp.Regexp
|
regex *regexp.Regexp
|
||||||
|
@ -30,21 +24,17 @@ type redirect struct {
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a redirect middleware.
|
// New creates a Redirect middleware.
|
||||||
func New(ctx context.Context, next http.Handler, config config.Redirect, name string) (http.Handler, error) {
|
func newRedirect(ctx context.Context, next http.Handler, regex string, replacement string, permanent bool, name string) (http.Handler, error) {
|
||||||
logger := middlewares.GetLogger(ctx, name, typeName)
|
re, err := regexp.Compile(regex)
|
||||||
logger.Debug("Creating middleware")
|
|
||||||
logger.Debugf("Setting up redirect %s -> %s", config.Regex, config.Replacement)
|
|
||||||
|
|
||||||
re, err := regexp.Compile(config.Regex)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &redirect{
|
return &redirect{
|
||||||
regex: re,
|
regex: re,
|
||||||
replacement: config.Replacement,
|
replacement: replacement,
|
||||||
permanent: config.Permanent,
|
permanent: permanent,
|
||||||
errHandler: utils.DefaultHandler,
|
errHandler: utils.DefaultHandler,
|
||||||
next: next,
|
next: next,
|
||||||
name: name,
|
name: name,
|
||||||
|
@ -122,11 +112,32 @@ func (m *moveHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
func rawURL(req *http.Request) string {
|
func rawURL(req *http.Request) string {
|
||||||
scheme := "http"
|
scheme := "http"
|
||||||
|
host := req.Host
|
||||||
|
port := ""
|
||||||
|
uri := req.RequestURI
|
||||||
|
|
||||||
|
schemeRegex := `^(https?):\/\/([\w\._-]+)(:\d+)?(.*)$`
|
||||||
|
re, _ := regexp.Compile(schemeRegex)
|
||||||
|
if re.Match([]byte(req.RequestURI)) {
|
||||||
|
match := re.FindStringSubmatch(req.RequestURI)
|
||||||
|
scheme = match[1]
|
||||||
|
|
||||||
|
if len(match[2]) > 0 {
|
||||||
|
host = match[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(match[3]) > 0 {
|
||||||
|
port = match[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = match[4]
|
||||||
|
}
|
||||||
|
|
||||||
if req.TLS != nil || isXForwardedHTTPS(req) {
|
if req.TLS != nil || isXForwardedHTTPS(req) {
|
||||||
scheme = "https"
|
scheme = "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join([]string{scheme, "://", req.Host, req.RequestURI}, "")
|
return strings.Join([]string{scheme, "://", host, port, uri}, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func isXForwardedHTTPS(request *http.Request) bool {
|
func isXForwardedHTTPS(request *http.Request) bool {
|
||||||
|
|
22
middlewares/redirect/redirect_regex.go
Normal file
22
middlewares/redirect/redirect_regex.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package redirect
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/config"
|
||||||
|
"github.com/containous/traefik/middlewares"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
typeRegexName = "RedirectRegex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRedirectRegex creates a redirect middleware.
|
||||||
|
func NewRedirectRegex(ctx context.Context, next http.Handler, conf config.RedirectRegex, name string) (http.Handler, error) {
|
||||||
|
logger := middlewares.GetLogger(ctx, name, typeRegexName)
|
||||||
|
logger.Debug("Creating middleware")
|
||||||
|
logger.Debugf("Setting up redirection from %s to %s", conf.Regex, conf.Replacement)
|
||||||
|
|
||||||
|
return newRedirect(ctx, next, conf.Regex, conf.Replacement, conf.Permanent, name)
|
||||||
|
}
|
|
@ -13,10 +13,10 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewRegexHandler(t *testing.T) {
|
func TestRedirectRegexHandler(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
config config.Redirect
|
config config.RedirectRegex
|
||||||
method string
|
method string
|
||||||
url string
|
url string
|
||||||
secured bool
|
secured bool
|
||||||
|
@ -26,7 +26,7 @@ func TestNewRegexHandler(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "simple redirection",
|
desc: "simple redirection",
|
||||||
config: config.Redirect{
|
config: config.RedirectRegex{
|
||||||
Regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`,
|
Regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`,
|
||||||
Replacement: "https://${1}bar$2:443$4",
|
Replacement: "https://${1}bar$2:443$4",
|
||||||
},
|
},
|
||||||
|
@ -36,7 +36,7 @@ func TestNewRegexHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "use request header",
|
desc: "use request header",
|
||||||
config: config.Redirect{
|
config: config.RedirectRegex{
|
||||||
Regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`,
|
Regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`,
|
||||||
Replacement: `https://${1}{{ .Request.Header.Get "X-Foo" }}$2:443$4`,
|
Replacement: `https://${1}{{ .Request.Header.Get "X-Foo" }}$2:443$4`,
|
||||||
},
|
},
|
||||||
|
@ -46,7 +46,7 @@ func TestNewRegexHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "URL doesn't match regex",
|
desc: "URL doesn't match regex",
|
||||||
config: config.Redirect{
|
config: config.RedirectRegex{
|
||||||
Regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`,
|
Regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`,
|
||||||
Replacement: "https://${1}bar$2:443$4",
|
Replacement: "https://${1}bar$2:443$4",
|
||||||
},
|
},
|
||||||
|
@ -55,7 +55,7 @@ func TestNewRegexHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "invalid rewritten URL",
|
desc: "invalid rewritten URL",
|
||||||
config: config.Redirect{
|
config: config.RedirectRegex{
|
||||||
Regex: `^(.*)$`,
|
Regex: `^(.*)$`,
|
||||||
Replacement: "http://192.168.0.%31/",
|
Replacement: "http://192.168.0.%31/",
|
||||||
},
|
},
|
||||||
|
@ -64,7 +64,7 @@ func TestNewRegexHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "invalid regex",
|
desc: "invalid regex",
|
||||||
config: config.Redirect{
|
config: config.RedirectRegex{
|
||||||
Regex: `^(.*`,
|
Regex: `^(.*`,
|
||||||
Replacement: "$1",
|
Replacement: "$1",
|
||||||
},
|
},
|
||||||
|
@ -73,7 +73,7 @@ func TestNewRegexHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HTTP to HTTPS permanent",
|
desc: "HTTP to HTTPS permanent",
|
||||||
config: config.Redirect{
|
config: config.RedirectRegex{
|
||||||
Regex: `^http://`,
|
Regex: `^http://`,
|
||||||
Replacement: "https://$1",
|
Replacement: "https://$1",
|
||||||
Permanent: true,
|
Permanent: true,
|
||||||
|
@ -84,7 +84,7 @@ func TestNewRegexHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HTTPS to HTTP permanent",
|
desc: "HTTPS to HTTP permanent",
|
||||||
config: config.Redirect{
|
config: config.RedirectRegex{
|
||||||
Regex: `https://foo`,
|
Regex: `https://foo`,
|
||||||
Replacement: "http://foo",
|
Replacement: "http://foo",
|
||||||
Permanent: true,
|
Permanent: true,
|
||||||
|
@ -96,7 +96,7 @@ func TestNewRegexHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HTTP to HTTPS",
|
desc: "HTTP to HTTPS",
|
||||||
config: config.Redirect{
|
config: config.RedirectRegex{
|
||||||
Regex: `http://foo:80`,
|
Regex: `http://foo:80`,
|
||||||
Replacement: "https://foo:443",
|
Replacement: "https://foo:443",
|
||||||
},
|
},
|
||||||
|
@ -106,7 +106,7 @@ func TestNewRegexHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HTTPS to HTTP",
|
desc: "HTTPS to HTTP",
|
||||||
config: config.Redirect{
|
config: config.RedirectRegex{
|
||||||
Regex: `https://foo:443`,
|
Regex: `https://foo:443`,
|
||||||
Replacement: "http://foo:80",
|
Replacement: "http://foo:80",
|
||||||
},
|
},
|
||||||
|
@ -117,7 +117,7 @@ func TestNewRegexHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HTTP to HTTP",
|
desc: "HTTP to HTTP",
|
||||||
config: config.Redirect{
|
config: config.RedirectRegex{
|
||||||
Regex: `http://foo:80`,
|
Regex: `http://foo:80`,
|
||||||
Replacement: "http://foo:88",
|
Replacement: "http://foo:88",
|
||||||
},
|
},
|
||||||
|
@ -127,7 +127,7 @@ func TestNewRegexHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HTTP to HTTP POST",
|
desc: "HTTP to HTTP POST",
|
||||||
config: config.Redirect{
|
config: config.RedirectRegex{
|
||||||
Regex: `^http://`,
|
Regex: `^http://`,
|
||||||
Replacement: "https://$1",
|
Replacement: "https://$1",
|
||||||
},
|
},
|
||||||
|
@ -138,7 +138,7 @@ func TestNewRegexHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HTTP to HTTP POST permanent",
|
desc: "HTTP to HTTP POST permanent",
|
||||||
config: config.Redirect{
|
config: config.RedirectRegex{
|
||||||
Regex: `^http://`,
|
Regex: `^http://`,
|
||||||
Replacement: "https://$1",
|
Replacement: "https://$1",
|
||||||
Permanent: true,
|
Permanent: true,
|
||||||
|
@ -156,7 +156,7 @@ func TestNewRegexHandler(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||||
handler, err := New(context.Background(), next, test.config, "traefikTest")
|
handler, err := NewRedirectRegex(context.Background(), next, test.config, "traefikTest")
|
||||||
|
|
||||||
if test.errorExpected {
|
if test.errorExpected {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
34
middlewares/redirect/redirect_scheme.go
Normal file
34
middlewares/redirect/redirect_scheme.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package redirect
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/middlewares"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
typeSchemeName = "RedirectScheme"
|
||||||
|
schemeRedirectRegex = `^(https?:\/\/)?([\w\._-]+)(:\d+)?(.*)$`
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRedirectScheme creates a new RedirectScheme middleware.
|
||||||
|
func NewRedirectScheme(ctx context.Context, next http.Handler, conf config.RedirectScheme, name string) (http.Handler, error) {
|
||||||
|
logger := middlewares.GetLogger(ctx, name, typeSchemeName)
|
||||||
|
logger.Debug("Creating middleware")
|
||||||
|
logger.Debugf("Setting up redirection to %s %s", conf.Scheme, conf.Port)
|
||||||
|
|
||||||
|
if len(conf.Scheme) == 0 {
|
||||||
|
return nil, errors.New("you must provide a target scheme")
|
||||||
|
}
|
||||||
|
|
||||||
|
port := ""
|
||||||
|
if len(conf.Port) > 0 && !(conf.Scheme == "http" && conf.Port == "80" || conf.Scheme == "https" && conf.Port == "443") {
|
||||||
|
port = ":" + conf.Port
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRedirect(ctx, next, schemeRedirectRegex, conf.Scheme+"://${2}"+port+"${4}", conf.Permanent, name)
|
||||||
|
}
|
250
middlewares/redirect/redirect_scheme_test.go
Normal file
250
middlewares/redirect/redirect_scheme_test.go
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
package redirect
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRedirectSchemeHandler(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
config config.RedirectScheme
|
||||||
|
method string
|
||||||
|
url string
|
||||||
|
secured bool
|
||||||
|
expectedURL string
|
||||||
|
expectedStatus int
|
||||||
|
errorExpected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Without scheme",
|
||||||
|
config: config.RedirectScheme{},
|
||||||
|
url: "http://foo",
|
||||||
|
errorExpected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTP to HTTPS",
|
||||||
|
config: config.RedirectScheme{
|
||||||
|
Scheme: "https",
|
||||||
|
},
|
||||||
|
url: "http://foo",
|
||||||
|
expectedURL: "https://foo",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTP with port to HTTPS without port",
|
||||||
|
config: config.RedirectScheme{
|
||||||
|
Scheme: "https",
|
||||||
|
},
|
||||||
|
url: "http://foo:8080",
|
||||||
|
expectedURL: "https://foo",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTP without port to HTTPS with port",
|
||||||
|
config: config.RedirectScheme{
|
||||||
|
Scheme: "https",
|
||||||
|
Port: "8443",
|
||||||
|
},
|
||||||
|
url: "http://foo",
|
||||||
|
expectedURL: "https://foo:8443",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTP with port to HTTPS with port",
|
||||||
|
config: config.RedirectScheme{
|
||||||
|
Scheme: "https",
|
||||||
|
Port: "8443",
|
||||||
|
},
|
||||||
|
url: "http://foo:8000",
|
||||||
|
expectedURL: "https://foo:8443",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTPS with port to HTTPS with port",
|
||||||
|
config: config.RedirectScheme{
|
||||||
|
Scheme: "https",
|
||||||
|
Port: "8443",
|
||||||
|
},
|
||||||
|
url: "https://foo:8000",
|
||||||
|
expectedURL: "https://foo:8443",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTPS with port to HTTPS without port",
|
||||||
|
config: config.RedirectScheme{
|
||||||
|
Scheme: "https",
|
||||||
|
},
|
||||||
|
url: "https://foo:8000",
|
||||||
|
expectedURL: "https://foo",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "redirection to HTTPS without port from an URL already in https",
|
||||||
|
config: config.RedirectScheme{
|
||||||
|
Scheme: "https",
|
||||||
|
},
|
||||||
|
url: "https://foo:8000/theother",
|
||||||
|
expectedURL: "https://foo/theother",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTP to HTTPS permanent",
|
||||||
|
config: config.RedirectScheme{
|
||||||
|
Scheme: "https",
|
||||||
|
Port: "8443",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
url: "http://foo",
|
||||||
|
expectedURL: "https://foo:8443",
|
||||||
|
expectedStatus: http.StatusMovedPermanently,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "to HTTP 80",
|
||||||
|
config: config.RedirectScheme{
|
||||||
|
Scheme: "http",
|
||||||
|
Port: "80",
|
||||||
|
},
|
||||||
|
url: "http://foo:80",
|
||||||
|
expectedURL: "http://foo",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTP to wss",
|
||||||
|
config: config.RedirectScheme{
|
||||||
|
Scheme: "wss",
|
||||||
|
Port: "9443",
|
||||||
|
},
|
||||||
|
url: "http://foo",
|
||||||
|
expectedURL: "wss://foo:9443",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTP to wss without port",
|
||||||
|
config: config.RedirectScheme{
|
||||||
|
Scheme: "wss",
|
||||||
|
},
|
||||||
|
url: "http://foo",
|
||||||
|
expectedURL: "wss://foo",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTP with port to wss without port",
|
||||||
|
config: config.RedirectScheme{
|
||||||
|
Scheme: "wss",
|
||||||
|
},
|
||||||
|
url: "http://foo:5678",
|
||||||
|
expectedURL: "wss://foo",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTP to HTTPS without port",
|
||||||
|
config: config.RedirectScheme{
|
||||||
|
Scheme: "https",
|
||||||
|
},
|
||||||
|
url: "http://foo:443",
|
||||||
|
expectedURL: "https://foo",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTP port redirection",
|
||||||
|
config: config.RedirectScheme{
|
||||||
|
Scheme: "http",
|
||||||
|
Port: "8181",
|
||||||
|
},
|
||||||
|
url: "http://foo:8080",
|
||||||
|
expectedURL: "http://foo:8181",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTPS with port 80 to HTTPS without port",
|
||||||
|
config: config.RedirectScheme{
|
||||||
|
Scheme: "https",
|
||||||
|
},
|
||||||
|
url: "https://foo:80",
|
||||||
|
expectedURL: "https://foo",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||||
|
handler, err := NewRedirectScheme(context.Background(), next, test.config, "traefikTest")
|
||||||
|
|
||||||
|
if test.errorExpected {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Nil(t, handler)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, handler)
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
method := http.MethodGet
|
||||||
|
if test.method != "" {
|
||||||
|
method = test.method
|
||||||
|
}
|
||||||
|
r := httptest.NewRequest(method, test.url, nil)
|
||||||
|
|
||||||
|
if test.secured {
|
||||||
|
r.TLS = &tls.ConnectionState{}
|
||||||
|
}
|
||||||
|
r.Header.Set("X-Foo", "bar")
|
||||||
|
handler.ServeHTTP(recorder, r)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expectedStatus, recorder.Code)
|
||||||
|
if test.expectedStatus == http.StatusMovedPermanently ||
|
||||||
|
test.expectedStatus == http.StatusFound ||
|
||||||
|
test.expectedStatus == http.StatusTemporaryRedirect ||
|
||||||
|
test.expectedStatus == http.StatusPermanentRedirect {
|
||||||
|
|
||||||
|
location, err := recorder.Result().Location()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expectedURL, location.String())
|
||||||
|
} else {
|
||||||
|
location, err := recorder.Result().Location()
|
||||||
|
require.Errorf(t, err, "Location %v", location)
|
||||||
|
}
|
||||||
|
|
||||||
|
schemeRegex := `^(https?):\/\/([\w\._-]+)(:\d+)?(.*)$`
|
||||||
|
re, _ := regexp.Compile(schemeRegex)
|
||||||
|
|
||||||
|
if re.Match([]byte(test.url)) {
|
||||||
|
match := re.FindStringSubmatch(test.url)
|
||||||
|
r.RequestURI = match[4]
|
||||||
|
|
||||||
|
handler.ServeHTTP(recorder, r)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expectedStatus, recorder.Code)
|
||||||
|
if test.expectedStatus == http.StatusMovedPermanently ||
|
||||||
|
test.expectedStatus == http.StatusFound ||
|
||||||
|
test.expectedStatus == http.StatusTemporaryRedirect ||
|
||||||
|
test.expectedStatus == http.StatusPermanentRedirect {
|
||||||
|
|
||||||
|
location, err := recorder.Result().Location()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expectedURL, location.String())
|
||||||
|
} else {
|
||||||
|
location, err := recorder.Result().Location()
|
||||||
|
require.Errorf(t, err, "Location %v", location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -90,9 +90,12 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||||
"traefik.middlewares.Middleware12.ratelimit.rateset.Rate1.average": "42",
|
"traefik.middlewares.Middleware12.ratelimit.rateset.Rate1.average": "42",
|
||||||
"traefik.middlewares.Middleware12.ratelimit.rateset.Rate1.burst": "42",
|
"traefik.middlewares.Middleware12.ratelimit.rateset.Rate1.burst": "42",
|
||||||
"traefik.middlewares.Middleware12.ratelimit.rateset.Rate1.period": "42",
|
"traefik.middlewares.Middleware12.ratelimit.rateset.Rate1.period": "42",
|
||||||
"traefik.middlewares.Middleware13.redirect.permanent": "true",
|
"traefik.middlewares.Middleware13.redirectregex.permanent": "true",
|
||||||
"traefik.middlewares.Middleware13.redirect.regex": "foobar",
|
"traefik.middlewares.Middleware13.redirectregex.regex": "foobar",
|
||||||
"traefik.middlewares.Middleware13.redirect.replacement": "foobar",
|
"traefik.middlewares.Middleware13.redirectregex.replacement": "foobar",
|
||||||
|
"traefik.middlewares.Middleware13b.redirectscheme.scheme": "https",
|
||||||
|
"traefik.middlewares.Middleware13b.redirectscheme.port": "80",
|
||||||
|
"traefik.middlewares.Middleware13b.redirectscheme.permanent": "true",
|
||||||
"traefik.middlewares.Middleware14.replacepath.path": "foobar",
|
"traefik.middlewares.Middleware14.replacepath.path": "foobar",
|
||||||
"traefik.middlewares.Middleware15.replacepathregex.regex": "foobar",
|
"traefik.middlewares.Middleware15.replacepathregex.regex": "foobar",
|
||||||
"traefik.middlewares.Middleware15.replacepathregex.replacement": "foobar",
|
"traefik.middlewares.Middleware15.replacepathregex.replacement": "foobar",
|
||||||
|
@ -237,12 +240,19 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Middleware13": {
|
"Middleware13": {
|
||||||
Redirect: &config.Redirect{
|
RedirectRegex: &config.RedirectRegex{
|
||||||
Regex: "foobar",
|
Regex: "foobar",
|
||||||
Replacement: "foobar",
|
Replacement: "foobar",
|
||||||
Permanent: true,
|
Permanent: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"Middleware13b": {
|
||||||
|
RedirectScheme: &config.RedirectScheme{
|
||||||
|
Scheme: "https",
|
||||||
|
Port: "80",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
"Middleware14": {
|
"Middleware14": {
|
||||||
ReplacePath: &config.ReplacePath{
|
ReplacePath: &config.ReplacePath{
|
||||||
Path: "foobar",
|
Path: "foobar",
|
||||||
|
@ -553,12 +563,19 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Middleware13": {
|
"Middleware13": {
|
||||||
Redirect: &config.Redirect{
|
RedirectRegex: &config.RedirectRegex{
|
||||||
Regex: "foobar",
|
Regex: "foobar",
|
||||||
Replacement: "foobar",
|
Replacement: "foobar",
|
||||||
Permanent: true,
|
Permanent: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"Middleware13b": {
|
||||||
|
RedirectScheme: &config.RedirectScheme{
|
||||||
|
Scheme: "https",
|
||||||
|
Port: "80",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
"Middleware14": {
|
"Middleware14": {
|
||||||
ReplacePath: &config.ReplacePath{
|
ReplacePath: &config.ReplacePath{
|
||||||
Path: "foobar",
|
Path: "foobar",
|
||||||
|
@ -856,9 +873,12 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||||
"traefik.Middlewares.Middleware12.RateLimit.RateSet.Rate1.Average": "42",
|
"traefik.Middlewares.Middleware12.RateLimit.RateSet.Rate1.Average": "42",
|
||||||
"traefik.Middlewares.Middleware12.RateLimit.RateSet.Rate1.Burst": "42",
|
"traefik.Middlewares.Middleware12.RateLimit.RateSet.Rate1.Burst": "42",
|
||||||
"traefik.Middlewares.Middleware12.RateLimit.RateSet.Rate1.Period": "42",
|
"traefik.Middlewares.Middleware12.RateLimit.RateSet.Rate1.Period": "42",
|
||||||
"traefik.Middlewares.Middleware13.Redirect.Permanent": "true",
|
"traefik.Middlewares.Middleware13.RedirectRegex.Regex": "foobar",
|
||||||
"traefik.Middlewares.Middleware13.Redirect.Regex": "foobar",
|
"traefik.Middlewares.Middleware13.RedirectRegex.Replacement": "foobar",
|
||||||
"traefik.Middlewares.Middleware13.Redirect.Replacement": "foobar",
|
"traefik.Middlewares.Middleware13.RedirectRegex.Permanent": "true",
|
||||||
|
"traefik.Middlewares.Middleware13b.RedirectScheme.Scheme": "https",
|
||||||
|
"traefik.Middlewares.Middleware13b.RedirectScheme.Port": "80",
|
||||||
|
"traefik.Middlewares.Middleware13b.RedirectScheme.Permanent": "true",
|
||||||
"traefik.Middlewares.Middleware14.ReplacePath.Path": "foobar",
|
"traefik.Middlewares.Middleware14.ReplacePath.Path": "foobar",
|
||||||
"traefik.Middlewares.Middleware15.ReplacePathRegex.Regex": "foobar",
|
"traefik.Middlewares.Middleware15.ReplacePathRegex.Regex": "foobar",
|
||||||
"traefik.Middlewares.Middleware15.ReplacePathRegex.Replacement": "foobar",
|
"traefik.Middlewares.Middleware15.ReplacePathRegex.Replacement": "foobar",
|
||||||
|
|
|
@ -248,11 +248,22 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string, c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect
|
// RedirectRegex
|
||||||
if config.Redirect != nil {
|
if config.RedirectRegex != nil {
|
||||||
if middleware == nil {
|
if middleware == nil {
|
||||||
middleware = func(next http.Handler) (http.Handler, error) {
|
middleware = func(next http.Handler) (http.Handler, error) {
|
||||||
return redirect.New(ctx, next, *config.Redirect, middlewareName)
|
return redirect.NewRedirectRegex(ctx, next, *config.RedirectRegex, middlewareName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, badConf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedirectScheme
|
||||||
|
if config.RedirectScheme != nil {
|
||||||
|
if middleware == nil {
|
||||||
|
middleware = func(next http.Handler) (http.Handler, error) {
|
||||||
|
return redirect.NewRedirectScheme(ctx, next, *config.RedirectScheme, middlewareName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil, badConf
|
return nil, badConf
|
||||||
|
|
Loading…
Reference in a new issue