fix(provider): handle invalid proxy upstreams safely
Avoid panicking when a ProxyProvider is constructed with a malformed upstream URL. The provider now records the configuration error and returns a standard 500 envelope when mounted. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
475027d716
commit
2fd17a432c
2 changed files with 58 additions and 8 deletions
|
|
@ -8,6 +8,7 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
coreapi "dappco.re/go/core/api"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
|
|
@ -39,14 +40,20 @@ type ProxyConfig struct {
|
|||
type ProxyProvider struct {
|
||||
config ProxyConfig
|
||||
proxy *httputil.ReverseProxy
|
||||
err error
|
||||
}
|
||||
|
||||
// NewProxy creates a ProxyProvider from the given configuration.
|
||||
// The upstream URL must be valid or NewProxy will panic.
|
||||
// Invalid upstream URLs do not panic; the provider retains the
|
||||
// configuration error and responds with a standard 500 envelope when
|
||||
// mounted. This keeps provider construction safe for callers.
|
||||
func NewProxy(cfg ProxyConfig) *ProxyProvider {
|
||||
target, err := url.Parse(cfg.Upstream)
|
||||
if err != nil {
|
||||
panic("provider.NewProxy: invalid upstream URL: " + err.Error())
|
||||
return &ProxyProvider{
|
||||
config: cfg,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||
|
|
@ -71,6 +78,15 @@ func NewProxy(cfg ProxyConfig) *ProxyProvider {
|
|||
}
|
||||
}
|
||||
|
||||
// Err reports any configuration error detected while constructing the proxy.
|
||||
// A nil error means the proxy is ready to mount and serve requests.
|
||||
func (p *ProxyProvider) Err() error {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return p.err
|
||||
}
|
||||
|
||||
// stripBasePath removes an exact base path prefix from a request path.
|
||||
// It only strips when the path matches the base path itself or lives under
|
||||
// the base path boundary, so "/api" will not accidentally trim "/api-v2".
|
||||
|
|
@ -112,6 +128,19 @@ func (p *ProxyProvider) BasePath() string {
|
|||
// RegisterRoutes mounts a catch-all reverse proxy handler on the router group.
|
||||
func (p *ProxyProvider) RegisterRoutes(rg *gin.RouterGroup) {
|
||||
rg.Any("/*path", func(c *gin.Context) {
|
||||
if p == nil || p.err != nil || p.proxy == nil {
|
||||
details := map[string]any{}
|
||||
if p != nil && p.err != nil {
|
||||
details["error"] = p.err.Error()
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, coreapi.FailWithDetails(
|
||||
"invalid_provider_configuration",
|
||||
"Provider is misconfigured",
|
||||
details,
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
// Use the underlying http.ResponseWriter directly. Gin's
|
||||
// responseWriter wrapper does not implement http.CloseNotifier,
|
||||
// which httputil.ReverseProxy requires for cancellation signalling.
|
||||
|
|
|
|||
|
|
@ -183,11 +183,32 @@ func TestProxyProvider_Renderable_Good(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestProxyProvider_Ugly_InvalidUpstream(t *testing.T) {
|
||||
assert.Panics(t, func() {
|
||||
provider.NewProxy(provider.ProxyConfig{
|
||||
Name: "bad",
|
||||
BasePath: "/api/v1/bad",
|
||||
Upstream: "://not-a-url",
|
||||
})
|
||||
p := provider.NewProxy(provider.ProxyConfig{
|
||||
Name: "bad",
|
||||
BasePath: "/api/v1/bad",
|
||||
Upstream: "://not-a-url",
|
||||
})
|
||||
|
||||
require.NotNil(t, p)
|
||||
assert.Error(t, p.Err())
|
||||
|
||||
engine, err := api.New()
|
||||
require.NoError(t, err)
|
||||
engine.Register(p)
|
||||
|
||||
handler := engine.Handler()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/bad/items", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
|
||||
var body map[string]any
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body))
|
||||
|
||||
assert.Equal(t, false, body["success"])
|
||||
errObj, ok := body["error"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "invalid_provider_configuration", errObj["code"])
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue