Enhance error handling in gateway and configuration
- Added `expose_upstream_errors` option to the configuration, allowing detailed error messages in JSON responses for 502 errors. - Implemented `writeBadGatewayJSON` function to streamline JSON error responses, including upstream error details when enabled. - Updated documentation to reflect changes in configuration and error handling behavior for improved diagnostics.
This commit is contained in:
@@ -8,6 +8,10 @@
|
|||||||
|
|
||||||
listen: ":8080"
|
listen: ":8080"
|
||||||
|
|
||||||
|
# Диагностика: при 502 от прокси в JSON добавляется error.detail с текстом ошибки RoundTrip (TLS, DNS, таймаут).
|
||||||
|
# Включайте только во внутренней сети — строка может содержать хосты/порты upstream.
|
||||||
|
# expose_upstream_errors: true
|
||||||
|
|
||||||
# If true, IP whitelist is not enforced (development only).
|
# If true, IP whitelist is not enforced (development only).
|
||||||
allow_all: false
|
allow_all: false
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,18 @@ servers:
|
|||||||
| `mihomo_base_url_env` | Имя переменной окружения; если задано и значение **непустое**, URL контроллера берётся из `os.Getenv` при старте (удобно в Docker без хардкода IP). Если env пустой, используется `mihomo_base_url`. |
|
| `mihomo_base_url_env` | Имя переменной окружения; если задано и значение **непустое**, URL контроллера берётся из `os.Getenv` при старте (удобно в Docker без хардкода IP). Если env пустой, используется `mihomo_base_url`. |
|
||||||
| `mihomo_authorization_env` | Имя env: **полное** значение заголовка `Authorization` (например `Bearer <secret>`), как у `authorization_env` для Telemt. Должно совпадать с секретом на стороне Mihomo (`secret` / `CLASH_SECRET` в конфиге ядра). |
|
| `mihomo_authorization_env` | Имя env: **полное** значение заголовка `Authorization` (например `Bearer <secret>`), как у `authorization_env` для Telemt. Должно совпадать с секретом на стороне Mihomo (`secret` / `CLASH_SECRET` в конфиге ядра). |
|
||||||
|
|
||||||
|
У **нескольких** записей `servers[]` можно задать **разные** имена переменных (`mihomo_authorization_env: TELEMT_MIHOMO_MTG` и `…_GT1`), если секреты контроллеров на нодах различаются. Одно и то же имя env для всех нод — нормально, если везде один и тот же токен.
|
||||||
|
|
||||||
|
#### Локальный Mihomo (HTTP в Docker) работает, удалённый (HTTPS) даёт 502 через шлюз
|
||||||
|
|
||||||
|
Прямой `curl` с хоста к `https://…:8443` может быть успешен, а шлюз при этом отдаёт `502` с `mihomo upstream unreachable`: исходящий запрос делает **процесс шлюза** (часто контейнер). Отличия от «рабочего» `curl`:
|
||||||
|
|
||||||
|
- другая сеть/DNS из контейнера;
|
||||||
|
- другой исходящий IP на стороне nginx Mihomo (whitelist `allow`);
|
||||||
|
- ошибка TLS при проверке сертификата из окружения процесса.
|
||||||
|
|
||||||
|
Включите в `config.yaml` **`expose_upstream_errors: true`**, перезапустите шлюз и повторите запрос: в JSON появится **`error.detail`** с текстом ошибки (`x509: …`, `dial tcp …`, `lookup …` и т.д.). После диагностики флаг отключите.
|
||||||
|
|
||||||
Правила:
|
Правила:
|
||||||
|
|
||||||
- Если указан `mihomo_base_url` или `mihomo_base_url_env`, обязательно задайте `mihomo_authorization_env` и непустые значения в env при старте шлюза.
|
- Если указан `mihomo_base_url` или `mihomo_base_url_env`, обязательно задайте `mihomo_authorization_env` и непустые значения в env при старте шлюза.
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ var aliasRe = regexp.MustCompile(`^[a-z0-9][a-z0-9_-]*$`)
|
|||||||
|
|
||||||
// Config is the gateway YAML configuration.
|
// Config is the gateway YAML configuration.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Listen string `yaml:"listen"`
|
Listen string `yaml:"listen"`
|
||||||
AllowAll bool `yaml:"allow_all"`
|
// ExposeUpstreamErrors puts RoundTrip error text in JSON 502 bodies (error.detail). For private ops only.
|
||||||
|
ExposeUpstreamErrors bool `yaml:"expose_upstream_errors"`
|
||||||
|
AllowAll bool `yaml:"allow_all"`
|
||||||
WhitelistCIDRs []string `yaml:"whitelist_cidrs"`
|
WhitelistCIDRs []string `yaml:"whitelist_cidrs"`
|
||||||
TrustedProxies []string `yaml:"trusted_proxies"`
|
TrustedProxies []string `yaml:"trusted_proxies"`
|
||||||
CorsAllowedOrigins []string `yaml:"cors_allowed_origins"`
|
CorsAllowedOrigins []string `yaml:"cors_allowed_origins"`
|
||||||
|
|||||||
+16
-12
@@ -36,6 +36,19 @@ type Gateway struct {
|
|||||||
webUI http.Handler
|
webUI http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeBadGatewayJSON(w http.ResponseWriter, expose bool, code, message string, upstreamErr error) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusBadGateway)
|
||||||
|
errObj := map[string]any{"code": code, "message": message}
|
||||||
|
if expose && upstreamErr != nil {
|
||||||
|
errObj["detail"] = upstreamErr.Error()
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"ok": false,
|
||||||
|
"error": errObj,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// NewGateway builds handlers and reverse proxies from parsed config.
|
// NewGateway builds handlers and reverse proxies from parsed config.
|
||||||
func NewGateway(p *config.Parsed, log *slog.Logger, geo *geoip.Service) (*Gateway, error) {
|
func NewGateway(p *config.Parsed, log *slog.Logger, geo *geoip.Service) (*Gateway, error) {
|
||||||
t := proxy.DirectTransport()
|
t := proxy.DirectTransport()
|
||||||
@@ -54,6 +67,7 @@ func NewGateway(p *config.Parsed, log *slog.Logger, geo *geoip.Service) (*Gatewa
|
|||||||
transport: t,
|
transport: t,
|
||||||
promHandler: promhttp.Handler(),
|
promHandler: promhttp.Handler(),
|
||||||
}
|
}
|
||||||
|
exposeErr := p.Config.ExposeUpstreamErrors
|
||||||
for i := range p.Config.Servers {
|
for i := range p.Config.Servers {
|
||||||
s := &p.Config.Servers[i]
|
s := &p.Config.Servers[i]
|
||||||
u, err := url.Parse(s.BaseURL)
|
u, err := url.Parse(s.BaseURL)
|
||||||
@@ -64,12 +78,7 @@ func NewGateway(p *config.Parsed, log *slog.Logger, geo *geoip.Service) (*Gatewa
|
|||||||
strip := "/api/" + s.Alias
|
strip := "/api/" + s.Alias
|
||||||
g.proxies[s.Alias] = proxy.NewAliasForward(u, strip, s.PathPrefix, auth, t, func(w http.ResponseWriter, r *http.Request, err error) {
|
g.proxies[s.Alias] = proxy.NewAliasForward(u, strip, s.PathPrefix, auth, t, func(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
log.Error("upstream error", "alias", s.Alias, "err", err)
|
log.Error("upstream error", "alias", s.Alias, "err", err)
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
writeBadGatewayJSON(w, exposeErr, "bad_gateway", "upstream unreachable", err)
|
||||||
w.WriteHeader(http.StatusBadGateway)
|
|
||||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
||||||
"ok": false,
|
|
||||||
"error": map[string]string{"code": "bad_gateway", "message": "upstream unreachable"},
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for alias, m := range p.MihomoByAlias {
|
for alias, m := range p.MihomoByAlias {
|
||||||
@@ -77,12 +86,7 @@ func NewGateway(p *config.Parsed, log *slog.Logger, geo *geoip.Service) (*Gatewa
|
|||||||
strip := "/api/" + a + "/mihomo"
|
strip := "/api/" + a + "/mihomo"
|
||||||
g.mihomo[a] = proxy.NewMihomoForward(m.Base, strip, m.Auth, t, func(w http.ResponseWriter, r *http.Request, err error) {
|
g.mihomo[a] = proxy.NewMihomoForward(m.Base, strip, m.Auth, t, func(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
log.Error("mihomo upstream error", "alias", a, "err", err)
|
log.Error("mihomo upstream error", "alias", a, "err", err)
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
writeBadGatewayJSON(w, exposeErr, "bad_gateway", "mihomo upstream unreachable", err)
|
||||||
w.WriteHeader(http.StatusBadGateway)
|
|
||||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
||||||
"ok": false,
|
|
||||||
"error": map[string]string{"code": "bad_gateway", "message": "mihomo upstream unreachable"},
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
var aggCacheTTL time.Duration
|
var aggCacheTTL time.Duration
|
||||||
|
|||||||
Reference in New Issue
Block a user