1
0
Fork 0

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,158 @@
package redirect
import (
"bytes"
"context"
"html/template"
"io"
"net/http"
"net/url"
"regexp"
"strings"
"github.com/containous/traefik/pkg/tracing"
"github.com/opentracing/opentracing-go/ext"
"github.com/vulcand/oxy/utils"
)
type redirect struct {
next http.Handler
regex *regexp.Regexp
replacement string
permanent bool
errHandler utils.ErrorHandler
name string
}
// New creates a Redirect middleware.
func newRedirect(_ context.Context, next http.Handler, regex string, replacement string, permanent bool, name string) (http.Handler, error) {
re, err := regexp.Compile(regex)
if err != nil {
return nil, err
}
return &redirect{
regex: re,
replacement: replacement,
permanent: permanent,
errHandler: utils.DefaultHandler,
next: next,
name: name,
}, nil
}
func (r *redirect) GetTracingInformation() (string, ext.SpanKindEnum) {
return r.name, tracing.SpanKindNoneEnum
}
func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
oldURL := rawURL(req)
// If the Regexp doesn't match, skip to the next handler
if !r.regex.MatchString(oldURL) {
r.next.ServeHTTP(rw, req)
return
}
// apply a rewrite regexp to the URL
newURL := r.regex.ReplaceAllString(oldURL, r.replacement)
// replace any variables that may be in there
rewrittenURL := &bytes.Buffer{}
if err := applyString(newURL, rewrittenURL, req); err != nil {
r.errHandler.ServeHTTP(rw, req, err)
return
}
// parse the rewritten URL and replace request URL with it
parsedURL, err := url.Parse(rewrittenURL.String())
if err != nil {
r.errHandler.ServeHTTP(rw, req, err)
return
}
if newURL != oldURL {
handler := &moveHandler{location: parsedURL, permanent: r.permanent}
handler.ServeHTTP(rw, req)
return
}
req.URL = parsedURL
// make sure the request URI corresponds the rewritten URL
req.RequestURI = req.URL.RequestURI()
r.next.ServeHTTP(rw, req)
}
type moveHandler struct {
location *url.URL
permanent bool
}
func (m *moveHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Location", m.location.String())
status := http.StatusFound
if req.Method != http.MethodGet {
status = http.StatusTemporaryRedirect
}
if m.permanent {
status = http.StatusMovedPermanently
if req.Method != http.MethodGet {
status = http.StatusPermanentRedirect
}
}
rw.WriteHeader(status)
_, err := rw.Write([]byte(http.StatusText(status)))
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func rawURL(req *http.Request) string {
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) {
scheme = "https"
}
return strings.Join([]string{scheme, "://", host, port, uri}, "")
}
func isXForwardedHTTPS(request *http.Request) bool {
xForwardedProto := request.Header.Get("X-Forwarded-Proto")
return len(xForwardedProto) > 0 && xForwardedProto == "https"
}
func applyString(in string, out io.Writer, req *http.Request) error {
t, err := template.New("t").Parse(in)
if err != nil {
return err
}
data := struct{ Request *http.Request }{Request: req}
return t.Execute(out, data)
}

View file

@ -0,0 +1,22 @@
package redirect
import (
"context"
"net/http"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/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)
}

View file

@ -0,0 +1,198 @@
package redirect
import (
"context"
"crypto/tls"
"net/http"
"net/http/httptest"
"testing"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/testhelpers"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRedirectRegexHandler(t *testing.T) {
testCases := []struct {
desc string
config config.RedirectRegex
method string
url string
secured bool
expectedURL string
expectedStatus int
errorExpected bool
}{
{
desc: "simple redirection",
config: config.RedirectRegex{
Regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`,
Replacement: "https://${1}bar$2:443$4",
},
url: "http://foo.com:80",
expectedURL: "https://foobar.com:443",
expectedStatus: http.StatusFound,
},
{
desc: "use request header",
config: config.RedirectRegex{
Regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`,
Replacement: `https://${1}{{ .Request.Header.Get "X-Foo" }}$2:443$4`,
},
url: "http://foo.com:80",
expectedURL: "https://foobar.com:443",
expectedStatus: http.StatusFound,
},
{
desc: "URL doesn't match regex",
config: config.RedirectRegex{
Regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`,
Replacement: "https://${1}bar$2:443$4",
},
url: "http://bar.com:80",
expectedStatus: http.StatusOK,
},
{
desc: "invalid rewritten URL",
config: config.RedirectRegex{
Regex: `^(.*)$`,
Replacement: "http://192.168.0.%31/",
},
url: "http://foo.com:80",
expectedStatus: http.StatusBadGateway,
},
{
desc: "invalid regex",
config: config.RedirectRegex{
Regex: `^(.*`,
Replacement: "$1",
},
url: "http://foo.com:80",
errorExpected: true,
},
{
desc: "HTTP to HTTPS permanent",
config: config.RedirectRegex{
Regex: `^http://`,
Replacement: "https://$1",
Permanent: true,
},
url: "http://foo",
expectedURL: "https://foo",
expectedStatus: http.StatusMovedPermanently,
},
{
desc: "HTTPS to HTTP permanent",
config: config.RedirectRegex{
Regex: `https://foo`,
Replacement: "http://foo",
Permanent: true,
},
secured: true,
url: "https://foo",
expectedURL: "http://foo",
expectedStatus: http.StatusMovedPermanently,
},
{
desc: "HTTP to HTTPS",
config: config.RedirectRegex{
Regex: `http://foo:80`,
Replacement: "https://foo:443",
},
url: "http://foo:80",
expectedURL: "https://foo:443",
expectedStatus: http.StatusFound,
},
{
desc: "HTTPS to HTTP",
config: config.RedirectRegex{
Regex: `https://foo:443`,
Replacement: "http://foo:80",
},
secured: true,
url: "https://foo:443",
expectedURL: "http://foo:80",
expectedStatus: http.StatusFound,
},
{
desc: "HTTP to HTTP",
config: config.RedirectRegex{
Regex: `http://foo:80`,
Replacement: "http://foo:88",
},
url: "http://foo:80",
expectedURL: "http://foo:88",
expectedStatus: http.StatusFound,
},
{
desc: "HTTP to HTTP POST",
config: config.RedirectRegex{
Regex: `^http://`,
Replacement: "https://$1",
},
url: "http://foo",
method: http.MethodPost,
expectedURL: "https://foo",
expectedStatus: http.StatusTemporaryRedirect,
},
{
desc: "HTTP to HTTP POST permanent",
config: config.RedirectRegex{
Regex: `^http://`,
Replacement: "https://$1",
Permanent: true,
},
url: "http://foo",
method: http.MethodPost,
expectedURL: "https://foo",
expectedStatus: http.StatusPermanentRedirect,
},
}
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 := NewRedirectRegex(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 := testhelpers.MustNewRequest(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)
}
}
})
}
}

View file

@ -0,0 +1,34 @@
package redirect
import (
"context"
"net/http"
"github.com/containous/traefik/pkg/middlewares"
"github.com/pkg/errors"
"github.com/containous/traefik/pkg/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)
}

View file

@ -0,0 +1,250 @@
package redirect
import (
"context"
"crypto/tls"
"net/http"
"net/http/httptest"
"regexp"
"testing"
"github.com/containous/traefik/pkg/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)
}
}
}
})
}
}