Refactor reverse proxy path handling and enhance documentation. Updated reverse.go to improve upstream URL construction and added a new test for nested API routes in reverse_test.go. Revised GATEWAY_RUN.md to clarify API path behavior and configuration requirements for Nginx. Adjusted comments in config.example.yaml for better understanding of HTTPS and API prefix usage.
This commit is contained in:
+1
-1
@@ -29,7 +29,7 @@ servers:
|
||||
path_prefix: /v1
|
||||
# authorization_env: TELEMT_API_AUTH
|
||||
|
||||
# ivx: API за префиксом /api/ на HTTPS.
|
||||
# ivx: HTTPS + nginx location /api/ → Telemt; base_url должен заканчиваться на /api/
|
||||
- alias: gt1
|
||||
base_url: https://gt1.ivx.su/api/
|
||||
path_prefix: /v1
|
||||
|
||||
@@ -178,6 +178,9 @@ docker compose down
|
||||
|
||||
## Устранение неполадок
|
||||
|
||||
- **Nginx с `location /api/` и `proxy_pass http://…:9091/;` (со слэшем в конце)** на бэкенд уходит путь **без** префикса `/api/` (например запрос к nginx `GET /api/v1/users` превращается в `GET /v1/users` на Telemt). Шлюз при `base_url: https://gt2.example/api/` должен запрашивать именно **`/api/v1/…`** на стороне nginx. Если в `base_url` нет пути `/api/` (только `https://gt2.example`), шлюз обратится к `https://gt2.example/v1/…` — часто это **не** попадает в `location /api/`, и nginx отдаёт **чужой vhost / заглушку**. Задавайте `base_url` с завершающим слэшем: `https://gt2.example/api/`.
|
||||
- **Заголовок `Host`**: шлюз выставляет `Host` равным хосту из `base_url` (как у обычного клиента к этому имени). Если после обновления образа проблема остаётся, с хоста шлюза проверьте: `curl -sv -o /dev/null https://gt2…/api/v1/health` и сравните с запросом через шлюз.
|
||||
- **`/v1/users` и `/v1/stats/users`** — разные маршруты в Telemt Control API. То, что открывается как `…/api/v1/stats/users` в браузере, через шлюз соответствует **`/api/{alias}/stats/users`**, а не `/api/{alias}/users`. См. матрицу путей в [API.md](API.md).
|
||||
- **`docker pull`: `unauthorized` / `denied`**: выполните `docker login git.shts.su` с учётной записью Gitea и PAT с **`read:package`**.
|
||||
- **`403 forbidden` с хоста при `allow_all: false`**: добавьте CIDR клиента в `whitelist_cidrs`. Запросы из контейнера к самому себе идут с `127.0.0.1` — при необходимости добавьте `127.0.0.1/32`.
|
||||
- **За reverse proxy**: укажите CIDR прокси в `trusted_proxies`, иначе whitelist видит IP прокси, а не клиента.
|
||||
|
||||
+27
-27
@@ -4,13 +4,12 @@ import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewReverseProxy builds a reverse proxy to target base URL with path rewriting:
|
||||
// stripPrefix (/api/{alias}) + pathPrefix (/v1) + remainder, prepended to target.Path
|
||||
// (so base_url https://host/api/ yields upstream /api/v1/...).
|
||||
// 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
|
||||
@@ -26,11 +25,14 @@ func NewReverseProxy(target *url.URL, stripPrefix, pathPrefix string, setAuth st
|
||||
}
|
||||
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 = ""
|
||||
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 {
|
||||
@@ -43,26 +45,24 @@ func NewReverseProxy(target *url.URL, stripPrefix, pathPrefix string, setAuth st
|
||||
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)
|
||||
func buildUpstreamURL(target *url.URL, pathPrefix, rest string) *url.URL {
|
||||
rel := strings.Trim(pathPrefix, "/")
|
||||
if rest != "" {
|
||||
if rel != "" {
|
||||
rel = rel + "/" + rest
|
||||
} else {
|
||||
rel = rest
|
||||
}
|
||||
}
|
||||
if p != "" {
|
||||
segs = append(segs, p)
|
||||
var parts []string
|
||||
for _, seg := range strings.Split(rel, "/") {
|
||||
if seg != "" {
|
||||
parts = append(parts, seg)
|
||||
}
|
||||
}
|
||||
if r != "" {
|
||||
segs = append(segs, r)
|
||||
if len(parts) == 0 {
|
||||
out := *target
|
||||
return &out
|
||||
}
|
||||
if len(segs) == 0 {
|
||||
return "/"
|
||||
}
|
||||
out := path.Join(segs...)
|
||||
if !strings.HasPrefix(out, "/") {
|
||||
out = "/" + out
|
||||
}
|
||||
return out
|
||||
return target.JoinPath(parts...)
|
||||
}
|
||||
|
||||
@@ -45,3 +45,24 @@ func TestReverseProxyPathRewriteWithAPIBasePath(t *testing.T) {
|
||||
t.Fatalf("status %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReverseProxyPathNestedStatsUsers(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/api/v1/stats/users" {
|
||||
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/gt2", "/v1", "")
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/gt2/stats/users", 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