commit 9882ad3edb31fa820887a22228074b83b8078c59 Author: Zeev Diukman Date: Wed Mar 5 09:31:02 2025 +0000 1st diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c9ebd11 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/zeevdiukman/go-reverseproxy + +go 1.24.0 diff --git a/reverseproxy.go b/reverseproxy.go new file mode 100644 index 0000000..3b62134 --- /dev/null +++ b/reverseproxy.go @@ -0,0 +1,70 @@ +package reverseproxy + +import ( + "context" + "net/http" + "net/http/httputil" + "net/url" + "strings" +) + +type ReverseProxy struct { + *httputil.ReverseProxy +} + +type CtxKey string + +func New(ctx context.Context, host string) *ReverseProxy { + ctxKey := CtxKey("host") + ctx = context.WithValue(ctx, ctxKey, host) + reverseproxy := &httputil.ReverseProxy{ + Director: func(r *http.Request) { + r = r.WithContext(ctx) + hostFromCtx := ctx.Value(ctxKey).(string) + target, _ := url.Parse(hostFromCtx) + targetQuery := target.RawQuery + r.URL.Scheme = target.Scheme + r.URL.Host = target.Host + r.URL.Path, r.URL.RawPath = joinURLPath(target, r.URL) + if targetQuery == "" || r.URL.RawQuery == "" { + r.URL.RawQuery = targetQuery + r.URL.RawQuery + } else { + r.URL.RawQuery = targetQuery + "&" + r.URL.RawQuery + } + }, + } + return &ReverseProxy{reverseproxy} +} + +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 +} + +func joinURLPath(a, b *url.URL) (path, rawpath string) { + if a.RawPath == "" && b.RawPath == "" { + return singleJoiningSlash(a.Path, b.Path), "" + } + // 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 +}