145 lines
3.3 KiB
Go
145 lines
3.3 KiB
Go
|
package redirect
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"net/http"
|
||
|
"net/url"
|
||
|
"regexp"
|
||
|
"strings"
|
||
|
"text/template"
|
||
|
|
||
|
"github.com/containous/traefik/configuration"
|
||
|
"github.com/urfave/negroni"
|
||
|
"github.com/vulcand/oxy/utils"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
defaultRedirectRegex = `^(?:https?:\/\/)?([\w\._-]+)(?::\d+)?(.*)$`
|
||
|
)
|
||
|
|
||
|
// NewEntryPointHandler create a new redirection handler base on entry point
|
||
|
func NewEntryPointHandler(dstEntryPoint *configuration.EntryPoint, permanent bool) (negroni.Handler, error) {
|
||
|
exp := regexp.MustCompile(`(:\d+)`)
|
||
|
match := exp.FindStringSubmatch(dstEntryPoint.Address)
|
||
|
if len(match) == 0 {
|
||
|
return nil, fmt.Errorf("bad Address format %q", dstEntryPoint.Address)
|
||
|
}
|
||
|
|
||
|
protocol := "http"
|
||
|
if dstEntryPoint.TLS != nil {
|
||
|
protocol = "https"
|
||
|
}
|
||
|
|
||
|
replacement := protocol + "://$1" + match[0] + "$2"
|
||
|
|
||
|
return NewRegexHandler(defaultRedirectRegex, replacement, permanent)
|
||
|
}
|
||
|
|
||
|
// NewRegexHandler create a new redirection handler base on regex
|
||
|
func NewRegexHandler(exp string, replacement string, permanent bool) (negroni.Handler, error) {
|
||
|
re, err := regexp.Compile(exp)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return &handler{
|
||
|
regexp: re,
|
||
|
replacement: replacement,
|
||
|
permanent: permanent,
|
||
|
errHandler: utils.DefaultHandler,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
type handler struct {
|
||
|
regexp *regexp.Regexp
|
||
|
replacement string
|
||
|
permanent bool
|
||
|
errHandler utils.ErrorHandler
|
||
|
}
|
||
|
|
||
|
func (h *handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
|
||
|
oldURL := rawURL(req)
|
||
|
|
||
|
// only continue if the Regexp param matches the URL
|
||
|
if !h.regexp.MatchString(oldURL) {
|
||
|
next.ServeHTTP(rw, req)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// apply a rewrite regexp to the URL
|
||
|
newURL := h.regexp.ReplaceAllString(oldURL, h.replacement)
|
||
|
|
||
|
// replace any variables that may be in there
|
||
|
rewrittenURL := &bytes.Buffer{}
|
||
|
if err := applyString(newURL, rewrittenURL, req); err != nil {
|
||
|
h.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 {
|
||
|
h.errHandler.ServeHTTP(rw, req, err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if newURL != oldURL {
|
||
|
handler := &moveHandler{location: parsedURL, permanent: h.permanent}
|
||
|
handler.ServeHTTP(rw, req)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
req.URL = parsedURL
|
||
|
|
||
|
// make sure the request URI corresponds the rewritten URL
|
||
|
req.RequestURI = req.URL.RequestURI()
|
||
|
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 m.permanent {
|
||
|
status = http.StatusMovedPermanently
|
||
|
}
|
||
|
rw.WriteHeader(status)
|
||
|
rw.Write([]byte(http.StatusText(status)))
|
||
|
}
|
||
|
|
||
|
func rawURL(request *http.Request) string {
|
||
|
scheme := "http"
|
||
|
if request.TLS != nil || isXForwardedHTTPS(request) {
|
||
|
scheme = "https"
|
||
|
}
|
||
|
|
||
|
return strings.Join([]string{scheme, "://", request.Host, request.RequestURI}, "")
|
||
|
}
|
||
|
|
||
|
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, request *http.Request) error {
|
||
|
t, err := template.New("t").Parse(in)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
data := struct {
|
||
|
Request *http.Request
|
||
|
}{
|
||
|
Request: request,
|
||
|
}
|
||
|
|
||
|
return t.Execute(out, data)
|
||
|
}
|