Skip to content

[ws-proxy] use ide-proxy to serve blobserve #10514

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions components/ws-proxy/pkg/proxy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
56 changes: 55 additions & 1 deletion components/ws-proxy/pkg/proxy/pass.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor Author

@iQQBot iQQBot Jun 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this part is copy from httputil.NewSingleHostReverseProxy the only different is add req.Host = target.Host this line, because we care about host header

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to add this comment as a comment in the source code itself 🙏

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{
Expand Down Expand Up @@ -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
Expand Down
19 changes: 9 additions & 10 deletions components/ws-proxy/pkg/proxy/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -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()))
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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,
Expand Down
24 changes: 12 additions & 12 deletions components/ws-proxy/pkg/proxy/routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: "<a href=\"https://blobserve.ws.test-domain.com/gitpod-io/supervisor:latest/__files__/favicon.ico\">See Other</a>.\n\n",
Body: "<a href=\"https://ide.test-domain.com/blobserve/gitpod-io/supervisor:latest/__files__/favicon.ico\">See Other</a>.\n\n",
},
},
{
Expand All @@ -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: "<a href=\"https://blobserve.ws.test-domain.com/gitpod-io/ide:latest/__files__/\">See Other</a>.\n\n",
Body: "<a href=\"https://ide.test-domain.com/blobserve/gitpod-io/ide:latest/__files__/\">See Other</a>.\n\n",
},
},
{
Expand All @@ -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",
},
},
{
Expand All @@ -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",
},
},
{
Expand Down Expand Up @@ -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: "<a href=\"https://blobserve.ws.test-domain.com/gitpod-io/supervisor:latest/__files__/main.js\">See Other</a>.\n\n",
Body: "<a href=\"https://ide.test-domain.com/blobserve/gitpod-io/supervisor:latest/__files__/main.js\">See Other</a>.\n\n",
},
},
{
Expand All @@ -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: "<a href=\"https://blobserve.ws.test-domain.com/gitpod-io/supervisor:latest/__files__/main.js\">See Other</a>.\n\n",
Body: "<a href=\"https://ide.test-domain.com/blobserve/gitpod-io/supervisor:latest/__files__/main.js\">See Other</a>.\n\n",
},
},
{
Expand Down
5 changes: 3 additions & 2 deletions install/installer/pkg/components/ws-proxy/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add a determination of the installation method here, and if it is full mode, use the internal svc method of communication

Host: fmt.Sprintf("ide.%s", ctx.Config.Domain),
PathPrefix: "/blobserve",
},
GitpodInstallation: &proxy.GitpodInstallation{
Scheme: "https",
Expand Down