diff --git a/components/ws-proxy/pkg/proxy/config.go b/components/ws-proxy/pkg/proxy/config.go
index 85ee8e894378c2..f0e2c57fc89c6e 100644
--- a/components/ws-proxy/pkg/proxy/config.go
+++ b/components/ws-proxy/pkg/proxy/config.go
@@ -120,8 +120,9 @@ func (c *GitpodInstallation) Validate() error {
// BlobServerConfig configures where to serve the IDE from.
type BlobServerConfig struct {
- Scheme string `json:"scheme"`
- Host string `json:"host"`
+ Scheme string `json:"scheme"`
+ Host string `json:"host"`
+ PathPrefix string `json:"pathPrefix"`
}
// Validate validates the configuration to catch issues during startup and not at runtime.
diff --git a/components/ws-proxy/pkg/proxy/pass.go b/components/ws-proxy/pkg/proxy/pass.go
index 5c1a1d0eb1d466..aa7208b479da43 100644
--- a/components/ws-proxy/pkg/proxy/pass.go
+++ b/components/ws-proxy/pkg/proxy/pass.go
@@ -44,6 +44,59 @@ type targetResolver func(*Config, WorkspaceInfoProvider, *http.Request) (*url.UR
type responseHandler func(*http.Response, *http.Request) error
+func singleJoiningSlash(a, b string) string {
+ aslash := strings.HasSuffix(a, "/")
+ bslash := strings.HasPrefix(b, "/")
+ switch {
+ case aslash && bslash:
+ return a + b[1:]
+ case !aslash && !bslash:
+ return a + "/" + b
+ }
+ return a + b
+}
+
+func joinURLPath(a, b *url.URL) (path, rawpath string) {
+ if a.RawPath == "" && b.RawPath == "" {
+ return singleJoiningSlash(a.Path, b.Path), ""
+ }
+ // Same as singleJoiningSlash, but uses EscapedPath to determine
+ // whether a slash should be added
+ apath := a.EscapedPath()
+ bpath := b.EscapedPath()
+
+ aslash := strings.HasSuffix(apath, "/")
+ bslash := strings.HasPrefix(bpath, "/")
+
+ switch {
+ case aslash && bslash:
+ return a.Path + b.Path[1:], apath + bpath[1:]
+ case !aslash && !bslash:
+ return a.Path + "/" + b.Path, apath + "/" + bpath
+ }
+ return a.Path + b.Path, apath + bpath
+}
+
+func NewSingleHostReverseProxy(target *url.URL) *httputil.ReverseProxy {
+ targetQuery := target.RawQuery
+ director := func(req *http.Request) {
+ req.URL.Scheme = target.Scheme
+ req.URL.Host = target.Host
+ req.Host = target.Host
+ req.URL.Path, req.URL.RawPath = joinURLPath(target, req.URL)
+ if targetQuery == "" || req.URL.RawQuery == "" {
+ req.URL.RawQuery = targetQuery + req.URL.RawQuery
+ } else {
+ req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
+ }
+ if _, ok := req.Header["User-Agent"]; !ok {
+ // explicitly disable User-Agent so it's not set to default value
+ req.Header.Set("User-Agent", "")
+ }
+ }
+ return &httputil.ReverseProxy{Director: director}
+}
+
// proxyPass is the function that assembles a ProxyHandler from the config, a resolver and various options and returns a http.HandlerFunc.
func proxyPass(config *RouteHandlerConfig, infoProvider WorkspaceInfoProvider, resolver targetResolver, opts ...proxyPassOpt) http.HandlerFunc {
h := proxyPassConfig{
@@ -76,7 +129,8 @@ func proxyPass(config *RouteHandlerConfig, infoProvider WorkspaceInfoProvider, r
originalURL := *req.URL
// TODO(cw): we should cache the proxy for some time for each target URL
- proxy := httputil.NewSingleHostReverseProxy(targetURL)
+
+ proxy := NewSingleHostReverseProxy(targetURL)
proxy.Transport = h.Transport
proxy.ModifyResponse = func(resp *http.Response) error {
url := resp.Request.URL
diff --git a/components/ws-proxy/pkg/proxy/routes.go b/components/ws-proxy/pkg/proxy/routes.go
index 438001e7f178f1..d7f085758e0bc7 100644
--- a/components/ws-proxy/pkg/proxy/routes.go
+++ b/components/ws-proxy/pkg/proxy/routes.go
@@ -239,7 +239,7 @@ func resolveSupervisorURL(cfg *Config, info *WorkspaceInfo, req *http.Request) (
var dst url.URL
dst.Scheme = cfg.BlobServer.Scheme
dst.Host = cfg.BlobServer.Host
- dst.Path = "/" + supervisorImage
+ dst.Path = cfg.BlobServer.PathPrefix + "/" + supervisorImage
return &dst, nil
}
@@ -269,7 +269,7 @@ func (ir *ideRoutes) HandleRoot(route *mux.Route) {
return ""
}
image := info.IDEImage
- imagePath := strings.TrimPrefix(req.URL.Path, "/"+image)
+ imagePath := strings.TrimPrefix(req.URL.Path, t.Config.BlobServer.PathPrefix+"/"+image)
if imagePath != "/index.html" && imagePath != "/" {
return image
}
@@ -283,7 +283,7 @@ func (ir *ideRoutes) HandleRoot(route *mux.Route) {
return image
}
supervisorURLString := supervisorURL.String() + "/main.js"
- preloadSupervisorReq, err := http.NewRequest("GET", supervisorURLString, nil)
+ preloadSupervisorReq, err := http.NewRequest("HEAD", supervisorURLString, nil)
if err != nil {
log.WithField("supervisorURL", supervisorURL).WithError(err).Error("could not preload supervisor")
return image
@@ -348,7 +348,7 @@ func installBlobserveRoutes(r *mux.Router, config *RouteHandlerConfig, infoProvi
var dst url.URL
dst.Scheme = cfg.BlobServer.Scheme
dst.Host = cfg.BlobServer.Host
- dst.Path = image
+ dst.Path = cfg.BlobServer.PathPrefix + "/" + strings.TrimPrefix(image, "/")
return &dst, nil
}
r.NewRoute().Handler(proxyPass(config, infoProvider, targetResolver, withLongTermCaching()))
@@ -424,7 +424,7 @@ func dynamicIDEResolver(config *Config, infoProvider WorkspaceInfoProvider, req
var dst url.URL
dst.Scheme = config.BlobServer.Scheme
dst.Host = config.BlobServer.Host
- dst.Path = "/" + info.IDEImage
+ dst.Path = config.BlobServer.PathPrefix + "/" + info.IDEImage
return &dst, nil
}
@@ -629,7 +629,7 @@ type blobserveTransport struct {
}
func (t *blobserveTransport) DoRoundTrip(req *http.Request) (resp *http.Response, err error) {
- for {
+ for i := 0; i < 5; i++ {
resp, err = t.transport.RoundTrip(req)
if err != nil {
return nil, err
@@ -702,7 +702,7 @@ func (t *blobserveTransport) RoundTrip(req *http.Request) (resp *http.Response,
}
func (t *blobserveTransport) redirect(image string, req *http.Request) (*http.Response, error) {
- path := strings.TrimPrefix(req.URL.Path, "/"+image)
+ path := strings.TrimPrefix(req.URL.Path, t.Config.BlobServer.PathPrefix+"/"+image)
location := t.asBlobserveURL(image, path)
header := make(http.Header, 2)
@@ -726,10 +726,9 @@ func (t *blobserveTransport) redirect(image string, req *http.Request) (*http.Re
}
func (t *blobserveTransport) asBlobserveURL(image string, path string) string {
- return fmt.Sprintf("%s://%s%s/%s%s%s",
+ return fmt.Sprintf("%s://ide.%s/blobserve/%s%s%s",
t.Config.GitpodInstallation.Scheme,
- "blobserve",
- t.Config.GitpodInstallation.WorkspaceHostSuffix,
+ t.Config.GitpodInstallation.HostName,
image,
imagePathSeparator,
path,
diff --git a/components/ws-proxy/pkg/proxy/routes_test.go b/components/ws-proxy/pkg/proxy/routes_test.go
index ea3f9b5dd9f0e2..1621af562d57c4 100644
--- a/components/ws-proxy/pkg/proxy/routes_test.go
+++ b/components/ws-proxy/pkg/proxy/routes_test.go
@@ -224,11 +224,11 @@ func TestRoutes(t *testing.T) {
Header: http.Header{
"Content-Type": {"text/html; charset=utf-8"},
"Location": {
- "https://blobserve.ws.test-domain.com/gitpod-io/supervisor:latest/__files__/favicon.ico",
+ "https://ide.test-domain.com/blobserve/gitpod-io/supervisor:latest/__files__/favicon.ico",
},
"Vary": {"Accept-Encoding"},
},
- Body: "See Other.\n\n",
+ Body: "See Other.\n\n",
},
},
{
@@ -241,10 +241,10 @@ func TestRoutes(t *testing.T) {
Status: http.StatusSeeOther,
Header: http.Header{
"Content-Type": {"text/html; charset=utf-8"},
- "Location": {"https://blobserve.ws.test-domain.com/gitpod-io/ide:latest/__files__/"},
+ "Location": {"https://ide.test-domain.com/blobserve/gitpod-io/ide:latest/__files__/"},
"Vary": {"Accept-Encoding"},
},
- Body: "See Other.\n\n",
+ Body: "See Other.\n\n",
},
},
{
@@ -257,11 +257,11 @@ func TestRoutes(t *testing.T) {
Expectation: Expectation{
Status: http.StatusOK,
Header: http.Header{
- "Content-Length": {"218"},
+ "Content-Length": {"220"},
"Content-Type": {"text/plain; charset=utf-8"},
"Vary": {"Accept-Encoding"},
},
- Body: "blobserve hit: /gitpod-io/ide:latest/\ninlineVars: {\"ide\":\"https://blobserve.ws.test-domain.com/gitpod-io/ide:latest/__files__\",\"supervisor\":\"https://blobserve.ws.test-domain.com/gitpod-io/supervisor:latest/__files__\"}\n",
+ Body: "blobserve hit: /gitpod-io/ide:latest/\ninlineVars: {\"ide\":\"https://ide.test-domain.com/blobserve/gitpod-io/ide:latest/__files__\",\"supervisor\":\"https://ide.test-domain.com/blobserve/gitpod-io/supervisor:latest/__files__\"}\n",
},
},
{
@@ -274,11 +274,11 @@ func TestRoutes(t *testing.T) {
Expectation: Expectation{
Status: http.StatusOK,
Header: http.Header{
- "Content-Length": {"218"},
+ "Content-Length": {"220"},
"Content-Type": {"text/plain; charset=utf-8"},
"Vary": {"Accept-Encoding"},
},
- Body: "blobserve hit: /gitpod-io/ide:latest/\ninlineVars: {\"ide\":\"https://blobserve.ws.test-domain.com/gitpod-io/ide:latest/__files__\",\"supervisor\":\"https://blobserve.ws.test-domain.com/gitpod-io/supervisor:latest/__files__\"}\n",
+ Body: "blobserve hit: /gitpod-io/ide:latest/\ninlineVars: {\"ide\":\"https://ide.test-domain.com/blobserve/gitpod-io/ide:latest/__files__\",\"supervisor\":\"https://ide.test-domain.com/blobserve/gitpod-io/supervisor:latest/__files__\"}\n",
},
},
{
@@ -467,10 +467,10 @@ func TestRoutes(t *testing.T) {
Status: http.StatusSeeOther,
Header: http.Header{
"Content-Type": {"text/html; charset=utf-8"},
- "Location": {"https://blobserve.ws.test-domain.com/gitpod-io/supervisor:latest/__files__/main.js"},
+ "Location": {"https://ide.test-domain.com/blobserve/gitpod-io/supervisor:latest/__files__/main.js"},
"Vary": {"Accept-Encoding"},
},
- Body: "See Other.\n\n",
+ Body: "See Other.\n\n",
},
},
{
@@ -493,10 +493,10 @@ func TestRoutes(t *testing.T) {
Status: http.StatusSeeOther,
Header: http.Header{
"Content-Type": {"text/html; charset=utf-8"},
- "Location": {"https://blobserve.ws.test-domain.com/gitpod-io/supervisor:latest/__files__/main.js"},
+ "Location": {"https://ide.test-domain.com/blobserve/gitpod-io/supervisor:latest/__files__/main.js"},
"Vary": {"Accept-Encoding"},
},
- Body: "See Other.\n\n",
+ Body: "See Other.\n\n",
},
},
{
diff --git a/install/installer/pkg/components/ws-proxy/configmap.go b/install/installer/pkg/components/ws-proxy/configmap.go
index 0501990fe454a9..b22421a2491ffe 100644
--- a/install/installer/pkg/components/ws-proxy/configmap.go
+++ b/install/installer/pkg/components/ws-proxy/configmap.go
@@ -44,8 +44,9 @@ func configmap(ctx *common.RenderContext) ([]runtime.Object, error) {
MaxIdleConnsPerHost: 100,
},
BlobServer: &proxy.BlobServerConfig{
- Scheme: "http",
- Host: fmt.Sprintf("blobserve.%s.svc.cluster.local:%d", ctx.Namespace, common.BlobServeServicePort),
+ Scheme: "https",
+ Host: fmt.Sprintf("ide.%s", ctx.Config.Domain),
+ PathPrefix: "/blobserve",
},
GitpodInstallation: &proxy.GitpodInstallation{
Scheme: "https",