Add an option to preserve server path
This commit is contained in:
parent
6e1f5dc071
commit
83871f27dd
19 changed files with 251 additions and 44 deletions
|
@ -15,15 +15,17 @@ import (
|
|||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
// StatusClientClosedRequest non-standard HTTP status code for client disconnection.
|
||||
const StatusClientClosedRequest = 499
|
||||
const (
|
||||
// StatusClientClosedRequest non-standard HTTP status code for client disconnection.
|
||||
StatusClientClosedRequest = 499
|
||||
|
||||
// StatusClientClosedRequestText non-standard HTTP status for client disconnection.
|
||||
const StatusClientClosedRequestText = "Client Closed Request"
|
||||
// StatusClientClosedRequestText non-standard HTTP status for client disconnection.
|
||||
StatusClientClosedRequestText = "Client Closed Request"
|
||||
)
|
||||
|
||||
func buildSingleHostProxy(target *url.URL, passHostHeader bool, flushInterval time.Duration, roundTripper http.RoundTripper, bufferPool httputil.BufferPool) http.Handler {
|
||||
func buildSingleHostProxy(target *url.URL, passHostHeader bool, preservePath bool, flushInterval time.Duration, roundTripper http.RoundTripper, bufferPool httputil.BufferPool) http.Handler {
|
||||
return &httputil.ReverseProxy{
|
||||
Director: directorBuilder(target, passHostHeader),
|
||||
Director: directorBuilder(target, passHostHeader, preservePath),
|
||||
Transport: roundTripper,
|
||||
FlushInterval: flushInterval,
|
||||
BufferPool: bufferPool,
|
||||
|
@ -31,7 +33,7 @@ func buildSingleHostProxy(target *url.URL, passHostHeader bool, flushInterval ti
|
|||
}
|
||||
}
|
||||
|
||||
func directorBuilder(target *url.URL, passHostHeader bool) func(req *http.Request) {
|
||||
func directorBuilder(target *url.URL, passHostHeader bool, preservePath bool) func(req *http.Request) {
|
||||
return func(outReq *http.Request) {
|
||||
outReq.URL.Scheme = target.Scheme
|
||||
outReq.URL.Host = target.Host
|
||||
|
@ -46,6 +48,11 @@ func directorBuilder(target *url.URL, passHostHeader bool) func(req *http.Reques
|
|||
|
||||
outReq.URL.Path = u.Path
|
||||
outReq.URL.RawPath = u.RawPath
|
||||
|
||||
if preservePath {
|
||||
outReq.URL.Path, outReq.URL.RawPath = JoinURLPath(target, u)
|
||||
}
|
||||
|
||||
// If a plugin/middleware adds semicolons in query params, they should be urlEncoded.
|
||||
outReq.URL.RawQuery = strings.ReplaceAll(u.RawQuery, ";", "&")
|
||||
outReq.RequestURI = "" // Outgoing request should not have RequestURI
|
||||
|
@ -54,7 +61,7 @@ func directorBuilder(target *url.URL, passHostHeader bool) func(req *http.Reques
|
|||
outReq.ProtoMajor = 1
|
||||
outReq.ProtoMinor = 1
|
||||
|
||||
// Do not pass client Host header unless optsetter PassHostHeader is set.
|
||||
// Do not pass client Host header unless option PassHostHeader is set.
|
||||
if !passHostHeader {
|
||||
outReq.Host = outReq.URL.Host
|
||||
}
|
||||
|
@ -106,6 +113,13 @@ func ErrorHandler(w http.ResponseWriter, req *http.Request, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
func statusText(statusCode int) string {
|
||||
if statusCode == StatusClientClosedRequest {
|
||||
return StatusClientClosedRequestText
|
||||
}
|
||||
return http.StatusText(statusCode)
|
||||
}
|
||||
|
||||
// ComputeStatusCode computes the HTTP status code according to the given error.
|
||||
func ComputeStatusCode(err error) int {
|
||||
switch {
|
||||
|
@ -127,9 +141,38 @@ func ComputeStatusCode(err error) int {
|
|||
return http.StatusInternalServerError
|
||||
}
|
||||
|
||||
func statusText(statusCode int) string {
|
||||
if statusCode == StatusClientClosedRequest {
|
||||
return StatusClientClosedRequestText
|
||||
// JoinURLPath computes the joined path and raw path of the given URLs.
|
||||
// From https://github.com/golang/go/blob/b521ebb55a9b26c8824b219376c7f91f7cda6ec2/src/net/http/httputil/reverseproxy.go#L221
|
||||
func JoinURLPath(a, b *url.URL) (path, rawpath string) {
|
||||
if a.RawPath == "" && b.RawPath == "" {
|
||||
return singleJoiningSlash(a.Path, b.Path), ""
|
||||
}
|
||||
return http.StatusText(statusCode)
|
||||
|
||||
// Same as singleJoiningSlash, but uses EscapedPath to determine
|
||||
// whether a slash should be added
|
||||
apath := a.EscapedPath()
|
||||
bpath := b.EscapedPath()
|
||||
|
||||
aslash := strings.HasSuffix(apath, "/")
|
||||
bslash := strings.HasPrefix(bpath, "/")
|
||||
|
||||
switch {
|
||||
case aslash && bslash:
|
||||
return a.Path + b.Path[1:], apath + bpath[1:]
|
||||
case !aslash && !bslash:
|
||||
return a.Path + "/" + b.Path, apath + "/" + bpath
|
||||
}
|
||||
return a.Path + b.Path, apath + bpath
|
||||
}
|
||||
|
||||
func singleJoiningSlash(a, b string) string {
|
||||
aslash := strings.HasSuffix(a, "/")
|
||||
bslash := strings.HasPrefix(b, "/")
|
||||
switch {
|
||||
case aslash && bslash:
|
||||
return a + b[1:]
|
||||
case !aslash && !bslash:
|
||||
return a + "/" + b
|
||||
}
|
||||
return a + b
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue