Refactor reverse proxy to support API base path and update example configuration. Changed base URLs in config.example.yaml to use HTTPS and the new /api/ prefix. Enhanced reverse proxy logic in reverse.go to build upstream paths correctly and added a new test for path rewriting with the API base path in reverse_test.go.
This commit is contained in:
+4
-4
@@ -29,15 +29,15 @@ servers:
|
|||||||
path_prefix: /v1
|
path_prefix: /v1
|
||||||
# authorization_env: TELEMT_API_AUTH
|
# authorization_env: TELEMT_API_AUTH
|
||||||
|
|
||||||
# ivx: порт 9091 как у типичного Telemt API; при необходимости — https и другой порт.
|
# ivx: API за префиксом /api/ на HTTPS.
|
||||||
- alias: gt1
|
- alias: gt1
|
||||||
base_url: http://gt1.ivx.su:9091
|
base_url: https://gt1.ivx.su/api/
|
||||||
path_prefix: /v1
|
path_prefix: /v1
|
||||||
|
|
||||||
- alias: gt2
|
- alias: gt2
|
||||||
base_url: http://gt2.ivx.su:9091
|
base_url: https://gt2.ivx.su/api/
|
||||||
path_prefix: /v1
|
path_prefix: /v1
|
||||||
|
|
||||||
- alias: gt3
|
- alias: gt3
|
||||||
base_url: http://gt3.ivx.su:9091
|
base_url: https://gt3.ivx.su/api/
|
||||||
path_prefix: /v1
|
path_prefix: /v1
|
||||||
|
|||||||
+45
-10
@@ -4,26 +4,37 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewReverseProxy builds a reverse proxy to target base URL with path rewriting:
|
// NewReverseProxy builds a reverse proxy to target base URL with path rewriting:
|
||||||
// stripPrefix (/api/{alias}) + pathPrefix (/v1) + remainder.
|
// stripPrefix (/api/{alias}) + pathPrefix (/v1) + remainder, prepended to target.Path
|
||||||
|
// (so base_url https://host/api/ yields upstream /api/v1/...).
|
||||||
func NewReverseProxy(target *url.URL, stripPrefix, pathPrefix string, setAuth string) *httputil.ReverseProxy {
|
func NewReverseProxy(target *url.URL, stripPrefix, pathPrefix string, setAuth string) *httputil.ReverseProxy {
|
||||||
proxy := httputil.NewSingleHostReverseProxy(target)
|
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||||
orig := proxy.Director
|
orig := proxy.Director
|
||||||
|
targetQuery := target.RawQuery
|
||||||
proxy.Director = func(req *http.Request) {
|
proxy.Director = func(req *http.Request) {
|
||||||
orig(req)
|
|
||||||
p := req.URL.Path
|
p := req.URL.Path
|
||||||
if strings.HasPrefix(p, stripPrefix) {
|
if !strings.HasPrefix(p, stripPrefix) {
|
||||||
rest := strings.TrimPrefix(p, stripPrefix)
|
orig(req)
|
||||||
rest = strings.TrimPrefix(rest, "/")
|
if setAuth != "" {
|
||||||
if rest == "" {
|
req.Header.Set("Authorization", setAuth)
|
||||||
req.URL.Path = pathPrefix
|
|
||||||
} else {
|
|
||||||
req.URL.Path = pathPrefix + "/" + rest
|
|
||||||
}
|
}
|
||||||
req.URL.RawPath = ""
|
return
|
||||||
|
}
|
||||||
|
rest := strings.TrimPrefix(p, stripPrefix)
|
||||||
|
rest = strings.TrimPrefix(rest, "/")
|
||||||
|
upPath := buildUpstreamPath(target.Path, pathPrefix, rest)
|
||||||
|
req.URL.Scheme = target.Scheme
|
||||||
|
req.URL.Host = target.Host
|
||||||
|
req.URL.Path = upPath
|
||||||
|
req.URL.RawPath = ""
|
||||||
|
if targetQuery == "" || req.URL.RawQuery == "" {
|
||||||
|
req.URL.RawQuery = targetQuery + req.URL.RawQuery
|
||||||
|
} else {
|
||||||
|
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
|
||||||
}
|
}
|
||||||
if setAuth != "" {
|
if setAuth != "" {
|
||||||
req.Header.Set("Authorization", setAuth)
|
req.Header.Set("Authorization", setAuth)
|
||||||
@@ -31,3 +42,27 @@ func NewReverseProxy(target *url.URL, stripPrefix, pathPrefix string, setAuth st
|
|||||||
}
|
}
|
||||||
return proxy
|
return proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildUpstreamPath(targetPath, pathPrefix, rest string) string {
|
||||||
|
base := strings.TrimSuffix(targetPath, "/")
|
||||||
|
p := strings.Trim(pathPrefix, "/")
|
||||||
|
r := strings.Trim(rest, "/")
|
||||||
|
var segs []string
|
||||||
|
if base != "" {
|
||||||
|
segs = append(segs, base)
|
||||||
|
}
|
||||||
|
if p != "" {
|
||||||
|
segs = append(segs, p)
|
||||||
|
}
|
||||||
|
if r != "" {
|
||||||
|
segs = append(segs, r)
|
||||||
|
}
|
||||||
|
if len(segs) == 0 {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
out := path.Join(segs...)
|
||||||
|
if !strings.HasPrefix(out, "/") {
|
||||||
|
out = "/" + out
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,3 +24,24 @@ func TestReverseProxyPathRewrite(t *testing.T) {
|
|||||||
t.Fatalf("status %d", rec.Code)
|
t.Fatalf("status %d", rec.Code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReverseProxyPathRewriteWithAPIBasePath(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/api/v1/health" {
|
||||||
|
t.Fatalf("path %q", r.URL.Path)
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
up, err := url.Parse(srv.URL + "/api/")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rp := NewReverseProxy(up, "/api/main_srv", "/v1", "")
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/main_srv/health", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
rp.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status %d", rec.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user