diff --git a/core/http/middleware/baseurl.go b/core/http/middleware/baseurl.go index 4ad04d46e..84f72cf69 100644 --- a/core/http/middleware/baseurl.go +++ b/core/http/middleware/baseurl.go @@ -55,6 +55,14 @@ func BasePathPrefix(c echo.Context) string { // The returned URL is guaranteed to end with `/`. // The method should be used in conjunction with the StripPathPrefix middleware. func BaseURL(c echo.Context) string { + // An explicit external base URL (LOCALAI_BASE_URL) is authoritative for + // the origin. The proxy-derived path prefix is still appended so a + // reverse-proxy mount point keeps working. Trailing slashes are + // normalized via BasePathPrefix, which always starts and ends with "/". + if ext, ok := c.Get("_external_base_url").(string); ok && ext != "" { + return strings.TrimRight(ext, "/") + BasePathPrefix(c) + } + fwdProto, fwdHost := parseForwarded(c.Request().Header.Get("Forwarded")) scheme := "http" diff --git a/core/http/middleware/baseurl_test.go b/core/http/middleware/baseurl_test.go index 72569aa67..be38865af 100644 --- a/core/http/middleware/baseurl_test.go +++ b/core/http/middleware/baseurl_test.go @@ -180,4 +180,51 @@ var _ = Describe("BaseURL", func() { Expect(actualURL).To(Equal("https://xfh.example/")) }) }) + + Context("explicit external base URL override", func() { + It("uses the configured origin over conflicting forwarded headers", func() { + app := echo.New() + actualURL := "" + app.GET("/x", func(c echo.Context) error { + c.Set("_external_base_url", "https://192.168.0.13:34567") + actualURL = BaseURL(c) + return nil + }) + req := httptest.NewRequest("GET", "/x", nil) + req.Header.Set("X-Forwarded-Proto", "http") + req.Header.Set("X-Forwarded-Host", "internal:8080") + rec := httptest.NewRecorder() + app.ServeHTTP(rec, req) + Expect(actualURL).To(Equal("https://192.168.0.13:34567/")) + }) + + It("combines the configured origin with a detected path prefix", func() { + app := echo.New() + actualURL := "" + app.GET("/hello", func(c echo.Context) error { + c.Set("_original_path", "/localai/hello") + c.Set("_external_base_url", "https://ext.example") + actualURL = BaseURL(c) + return nil + }) + req := httptest.NewRequest("GET", "/hello", nil) + rec := httptest.NewRecorder() + app.ServeHTTP(rec, req) + Expect(actualURL).To(Equal("https://ext.example/localai/")) + }) + + It("ignores an empty override", func() { + app := echo.New() + actualURL := "" + app.GET("/x", func(c echo.Context) error { + c.Set("_external_base_url", "") + actualURL = BaseURL(c) + return nil + }) + req := httptest.NewRequest("GET", "/x", nil) + rec := httptest.NewRecorder() + app.ServeHTTP(rec, req) + Expect(actualURL).To(Equal("http://example.com/")) + }) + }) })