mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 18:51:03 +01:00
206 lines
6.0 KiB
Go
206 lines
6.0 KiB
Go
package host
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/kataras/iris/v12/core/netutil"
|
|
)
|
|
|
|
// ProxyHandler returns a new ReverseProxy that rewrites
|
|
// URLs to the scheme, host, and base path provided in target. If the
|
|
// target's path is "/base" and the incoming request was for "/dir",
|
|
// the target request will be for /base/dir.
|
|
//
|
|
// Relative to httputil.NewSingleHostReverseProxy with some additions.
|
|
//
|
|
// Look `ProxyHandlerRemote` too.
|
|
func ProxyHandler(target *url.URL, config *tls.Config) *httputil.ReverseProxy {
|
|
if config == nil {
|
|
config = &tls.Config{MinVersion: tls.VersionTLS13}
|
|
}
|
|
|
|
director := func(req *http.Request) {
|
|
modifyProxiedRequest(req, target)
|
|
req.Host = target.Host
|
|
req.URL.Path = path.Join(target.Path, req.URL.Path)
|
|
}
|
|
|
|
// TODO: when go 1.20 released:
|
|
/*
|
|
rewrite := func(r *httputil.ProxyRequest) {
|
|
r.SetURL(target) // Forward request to outboundURL.
|
|
r.SetXForwarded() // Set X-Forwarded-* headers.
|
|
// r.Out.Header.Set("X-Additional-Header", "header set by the proxy")
|
|
// To preserve the inbound request's Host header (the default behavior of NewSingleHostReverseProxy):
|
|
// r.Out.Host = r.In.Host
|
|
}
|
|
*/
|
|
|
|
p := &httputil.ReverseProxy{Director: director /*, Rewrite: rewrite */}
|
|
|
|
if netutil.IsLoopbackHost(target.Host) {
|
|
transport := &http.Transport{
|
|
TLSClientConfig: config, // lint:ignore
|
|
}
|
|
p.Transport = transport
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
// mergeQuery return a query string that combines targetQuery and reqQuery
|
|
// and remove the duplicated query parameters of them.
|
|
func mergeQuery(targetQuery, reqQuery string) string {
|
|
var paramSlice []string
|
|
if targetQuery != "" {
|
|
paramSlice = strings.Split(targetQuery, "&")
|
|
}
|
|
|
|
if reqQuery != "" {
|
|
paramSlice = append(paramSlice, strings.Split(reqQuery, "&")...)
|
|
}
|
|
|
|
var mergedSlice []string
|
|
queryMap := make(map[string]bool)
|
|
for _, param := range paramSlice {
|
|
size := len(queryMap)
|
|
queryMap[param] = true
|
|
if size != len(queryMap) {
|
|
mergedSlice = append(mergedSlice, param)
|
|
}
|
|
}
|
|
return strings.Join(mergedSlice, "&")
|
|
}
|
|
|
|
func modifyProxiedRequest(req *http.Request, target *url.URL) {
|
|
req.URL.Scheme = target.Scheme
|
|
req.URL.Host = target.Host
|
|
req.URL.RawQuery = mergeQuery(target.RawQuery, req.URL.RawQuery)
|
|
|
|
if _, ok := req.Header["User-Agent"]; !ok {
|
|
// explicitly disable User-Agent so it's not set to default value
|
|
req.Header.Set("User-Agent", "")
|
|
}
|
|
}
|
|
|
|
// ProxyHandlerRemote returns a new ReverseProxy that rewrites
|
|
// URLs to the scheme, host, and path provided in target.
|
|
// Case 1: req.Host == target.Host
|
|
// behavior same as ProxyHandler
|
|
// Case 2: req.Host != target.Host
|
|
// the target request will be forwarded to the target's url
|
|
// insecureSkipVerify indicates enable ssl certificate verification or not.
|
|
//
|
|
// Look `ProxyHandler` too.
|
|
func ProxyHandlerRemote(target *url.URL, config *tls.Config) *httputil.ReverseProxy {
|
|
if config == nil {
|
|
config = &tls.Config{MinVersion: tls.VersionTLS13}
|
|
}
|
|
|
|
director := func(req *http.Request) {
|
|
modifyProxiedRequest(req, target)
|
|
|
|
if req.Host != target.Host {
|
|
req.URL.Path = target.Path
|
|
} else {
|
|
req.URL.Path = path.Join(target.Path, req.URL.Path)
|
|
}
|
|
|
|
req.Host = target.Host
|
|
}
|
|
p := &httputil.ReverseProxy{Director: director}
|
|
|
|
if netutil.IsLoopbackHost(target.Host) {
|
|
config.InsecureSkipVerify = true
|
|
}
|
|
|
|
transport := &http.Transport{
|
|
TLSClientConfig: config, // lint:ignore
|
|
}
|
|
p.Transport = transport
|
|
return p
|
|
}
|
|
|
|
// NewProxy returns a new host (server supervisor) which
|
|
// proxies all requests to the target.
|
|
// It uses the httputil.NewSingleHostReverseProxy.
|
|
//
|
|
// Usage:
|
|
// target, _ := url.Parse("https://mydomain.com")
|
|
// proxy := NewProxy("mydomain.com:80", target)
|
|
// proxy.ListenAndServe() // use of `proxy.Shutdown` to close the proxy server.
|
|
func NewProxy(hostAddr string, target *url.URL, config *tls.Config) *Supervisor {
|
|
proxyHandler := ProxyHandler(target, config)
|
|
proxy := New(&http.Server{
|
|
Addr: hostAddr,
|
|
Handler: proxyHandler,
|
|
})
|
|
|
|
return proxy
|
|
}
|
|
|
|
// NewProxyRemote returns a new host (server supervisor) which
|
|
// proxies all requests to the target.
|
|
// It uses the httputil.NewSingleHostReverseProxy.
|
|
//
|
|
// Usage:
|
|
// target, _ := url.Parse("https://anotherdomain.com/abc")
|
|
// proxy := NewProxyRemote("mydomain.com", target, false)
|
|
// proxy.ListenAndServe() // use of `proxy.Shutdown` to close the proxy server.
|
|
func NewProxyRemote(hostAddr string, target *url.URL, config *tls.Config) *Supervisor {
|
|
proxyHandler := ProxyHandlerRemote(target, config)
|
|
proxy := New(&http.Server{
|
|
Addr: hostAddr,
|
|
Handler: proxyHandler,
|
|
})
|
|
|
|
return proxy
|
|
}
|
|
|
|
// NewRedirection returns a new host (server supervisor) which
|
|
// redirects all requests to the target.
|
|
// Usage:
|
|
// target, _ := url.Parse("https://mydomain.com")
|
|
// r := NewRedirection(":80", target, 307)
|
|
// r.ListenAndServe() // use of `r.Shutdown` to close this server.
|
|
func NewRedirection(hostAddr string, target *url.URL, redirectStatus int) *Supervisor {
|
|
redirectSrv := &http.Server{
|
|
ReadTimeout: 30 * time.Second,
|
|
WriteTimeout: 60 * time.Second,
|
|
Addr: hostAddr,
|
|
Handler: RedirectHandler(target, redirectStatus),
|
|
}
|
|
|
|
return New(redirectSrv)
|
|
}
|
|
|
|
// RedirectHandler returns a simple redirect handler.
|
|
// See `NewProxy` or `ProxyHandler` for more features.
|
|
func RedirectHandler(target *url.URL, redirectStatus int) http.Handler {
|
|
targetURI := target.String()
|
|
if redirectStatus <= 300 {
|
|
// here we should use StatusPermanentRedirect but
|
|
// that may result on unexpected behavior
|
|
// for end-developers who might change their minds
|
|
// after a while, so keep status temporary.
|
|
// Note thatwe could also use StatusFound
|
|
// as we do on the `Context#Redirect`.
|
|
// It will also help us to prevent any post data issues.
|
|
redirectStatus = http.StatusTemporaryRedirect
|
|
}
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
redirectTo := path.Join(targetURI, r.URL.Path)
|
|
if len(r.URL.RawQuery) > 0 {
|
|
redirectTo += "?" + r.URL.RawQuery
|
|
}
|
|
http.Redirect(w, r, redirectTo, redirectStatus)
|
|
})
|
|
}
|