traefik/middlewares/redirect/redirect.go

159 lines
3.5 KiB
Go
Raw Normal View History

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