traefik/pkg/middlewares/redirect/redirect.go
2024-01-08 09:10:06 +01:00

110 lines
2.4 KiB
Go

package redirect
import (
"net/http"
"net/url"
"regexp"
"github.com/vulcand/oxy/v2/utils"
"go.opentelemetry.io/otel/trace"
)
const (
schemeHTTP = "http"
schemeHTTPS = "https"
)
const typeName = "Redirect"
var uriRegexp = regexp.MustCompile(`^(https?):\/\/(\[[\w:.]+\]|[\w\._-]+)?(:\d+)?(.*)$`)
type redirect struct {
next http.Handler
regex *regexp.Regexp
replacement string
permanent bool
errHandler utils.ErrorHandler
name string
rawURL func(*http.Request) string
}
// New creates a Redirect middleware.
func newRedirect(next http.Handler, regex, replacement string, permanent bool, rawURL func(*http.Request) string, 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,
rawURL: rawURL,
}, nil
}
func (r *redirect) GetTracingInformation() (string, string, trace.SpanKind) {
return r.name, typeName, trace.SpanKindInternal
}
func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
oldURL := r.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)
// Parse the rewritten URL and replace request URL with it.
parsedURL, err := url.Parse(newURL)
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)
}
}