72 lines
1.9 KiB
Go
72 lines
1.9 KiB
Go
package proxy
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
// NewReverseProxy builds a reverse proxy to target base URL with path rewriting:
|
|
// stripPrefix (/api/{alias}) + pathPrefix (/v1) + remainder, joined onto target via url.JoinPath
|
|
// (e.g. https://host/api/ + v1 + users → https://host/api/v1/users).
|
|
func NewReverseProxy(target *url.URL, stripPrefix, pathPrefix string, setAuth string) *httputil.ReverseProxy {
|
|
proxy := httputil.NewSingleHostReverseProxy(target)
|
|
orig := proxy.Director
|
|
targetQuery := target.RawQuery
|
|
proxy.Director = func(req *http.Request) {
|
|
p := req.URL.Path
|
|
if !strings.HasPrefix(p, stripPrefix) {
|
|
orig(req)
|
|
if setAuth != "" {
|
|
req.Header.Set("Authorization", setAuth)
|
|
}
|
|
// Server-side requests carry RequestURI; client RoundTrip rejects it with URL.Host set.
|
|
req.RequestURI = ""
|
|
return
|
|
}
|
|
rest := strings.TrimPrefix(p, stripPrefix)
|
|
rest = strings.TrimPrefix(rest, "/")
|
|
joined := buildUpstreamURL(target, pathPrefix, rest)
|
|
req.URL.Scheme = joined.Scheme
|
|
req.URL.Host = joined.Host
|
|
req.URL.Path = joined.Path
|
|
req.URL.RawPath = joined.RawPath
|
|
req.URL.Opaque = ""
|
|
// Client must send the same Host as the TLS SNI / nginx server_name.
|
|
req.Host = joined.Host
|
|
if targetQuery == "" || req.URL.RawQuery == "" {
|
|
req.URL.RawQuery = targetQuery + req.URL.RawQuery
|
|
} else {
|
|
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
|
|
}
|
|
if setAuth != "" {
|
|
req.Header.Set("Authorization", setAuth)
|
|
}
|
|
req.RequestURI = ""
|
|
}
|
|
return proxy
|
|
}
|
|
|
|
func buildUpstreamURL(target *url.URL, pathPrefix, rest string) *url.URL {
|
|
rel := strings.Trim(pathPrefix, "/")
|
|
if rest != "" {
|
|
if rel != "" {
|
|
rel = rel + "/" + rest
|
|
} else {
|
|
rel = rest
|
|
}
|
|
}
|
|
var parts []string
|
|
for _, seg := range strings.Split(rel, "/") {
|
|
if seg != "" {
|
|
parts = append(parts, seg)
|
|
}
|
|
}
|
|
if len(parts) == 0 {
|
|
out := *target
|
|
return &out
|
|
}
|
|
return target.JoinPath(parts...)
|
|
}
|