Skip to content

Commit 9317b88

Browse files
committed
wip
1 parent b983b62 commit 9317b88

File tree

3 files changed

+181
-20
lines changed

3 files changed

+181
-20
lines changed

components/proxy/plugins/frontend_dev/frontend_dev.go

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22
// Licensed under the GNU Affero General Public License (AGPL).
33
// See License.AGPL.txt in the project root for license information.
44

5-
package workspacedownload
5+
package frontend_dev
66

77
import (
8+
"bytes"
89
"fmt"
10+
"io"
911
"net/http"
1012
"net/http/httputil"
1113
"net/url"
1214
"os"
15+
"regexp"
1316
"strings"
1417

1518
"github.com/caddyserver/caddy/v2"
@@ -60,29 +63,88 @@ func (m Config) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp
6063
return caddyhttp.Error(http.StatusInternalServerError, fmt.Errorf("unexpected error forwarding to dev URL"))
6164
}
6265

63-
targetQuery := devURL.RawQuery
64-
director := func(req *http.Request) {
65-
req.URL.Scheme = devURL.Scheme
66-
req.URL.Host = devURL.Host
67-
req.Host = devURL.Host // override host header so target proxy can handle this request properly
68-
69-
req.URL.Path, req.URL.RawPath = joinURLPath(devURL, req.URL)
70-
if targetQuery == "" || req.URL.RawQuery == "" {
71-
req.URL.RawQuery = targetQuery + req.URL.RawQuery
72-
} else {
73-
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
74-
}
75-
if _, ok := req.Header["User-Agent"]; !ok {
76-
// explicitly disable User-Agent so it's not set to default value
77-
req.Header.Set("User-Agent", "")
78-
}
79-
}
80-
proxy := httputil.ReverseProxy{Director: director}
66+
// targetQuery := devURL.RawQuery
67+
// director := func(req *http.Request) {
68+
// req.URL.Scheme = devURL.Scheme
69+
// req.URL.Host = devURL.Host
70+
// req.Host = devURL.Host // override host header so target proxy can handle this request properly
71+
72+
// req.URL.Path, req.URL.RawPath = joinURLPath(devURL, req.URL)
73+
// if targetQuery == "" || req.URL.RawQuery == "" {
74+
// req.URL.RawQuery = targetQuery + req.URL.RawQuery
75+
// } else {
76+
// req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
77+
// }
78+
// if _, ok := req.Header["User-Agent"]; !ok {
79+
// // explicitly disable User-Agent so it's not set to default value
80+
// req.Header.Set("User-Agent", "")
81+
// }
82+
// }
83+
proxy := httputil.ReverseProxy{Transport: &RedirectingTransport{baseUrl: devURL}}
8184
proxy.ServeHTTP(w, r)
8285

8386
return nil
8487
}
8588

89+
type RedirectingTransport struct {
90+
baseUrl *url.URL
91+
}
92+
93+
func (rt *RedirectingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
94+
resp, err := http.DefaultTransport.RoundTrip(req)
95+
if err != nil {
96+
return nil, err
97+
}
98+
99+
// gpl: Do we have better means to avoid checking the body?
100+
if resp.StatusCode < 300 {
101+
modifiedResp := MatchAndRewriteRootRequest(resp, rt.baseUrl)
102+
if modifiedResp != nil {
103+
return modifiedResp, nil
104+
}
105+
}
106+
107+
return resp, nil
108+
}
109+
110+
func MatchAndRewriteRootRequest(or *http.Response, baseUrl *url.URL) *http.Response {
111+
// match index.html?
112+
prefix := []byte("<!doctype html>")
113+
var buf bytes.Buffer
114+
bodyReader := io.TeeReader(or.Body, &buf)
115+
prefixBuf := make([]byte, len(prefix))
116+
_, err := io.ReadAtLeast(bodyReader, prefixBuf, len(prefix))
117+
if err != nil {
118+
caddy.Log().Sugar().Warnf("prefix match: can't read response body: %w", err)
119+
return nil
120+
}
121+
if !bytes.Equal(prefix, prefixBuf) {
122+
return nil
123+
}
124+
125+
caddy.Log().Sugar().Infof("match index.html")
126+
_, err = io.Copy(&buf, or.Body)
127+
if err != nil {
128+
caddy.Log().Sugar().Errorf("unable to copy response body: %w, path: %s", err, or.Request.URL.Path)
129+
return nil
130+
}
131+
fullBody := buf.String()
132+
133+
mainJs := regexp.MustCompile(`"[^"]+?main\.[0-9a-z]+\.js"`)
134+
fullBody = mainJs.ReplaceAllStringFunc(fullBody, func(s string) string {
135+
return fmt.Sprintf(`"%s/static/js/main.js"`, baseUrl.String())
136+
})
137+
138+
mainCss := regexp.MustCompile(`<link[^>]+?rel="stylesheet">`)
139+
fullBody = mainCss.ReplaceAllString(fullBody, "")
140+
141+
hrefs := regexp.MustCompile(`href="/`)
142+
fullBody = hrefs.ReplaceAllString(fullBody, fmt.Sprintf(`href="%s/`, baseUrl.String()))
143+
144+
or.Body = io.NopCloser(strings.NewReader(fullBody))
145+
return or
146+
}
147+
86148
func joinURLPath(a, b *url.URL) (path, rawpath string) {
87149
if a.RawPath == "" && b.RawPath == "" {
88150
return singleJoiningSlash(a.Path, b.Path), ""
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright (c) 2023 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License.AGPL.txt in the project root for license information.
4+
5+
package frontend_dev
6+
7+
import (
8+
"io/ioutil"
9+
"net/http"
10+
"net/url"
11+
"strings"
12+
"testing"
13+
)
14+
15+
const index_html = `<!doctype html>
16+
<html lang="en">
17+
18+
<head>
19+
<meta charset="utf-8" />
20+
<link rel="icon" href="/favicon256.png" />
21+
<meta name="viewport" content="width=device-width,initial-scale=1" />
22+
<meta name="theme-color" content="#000000" />
23+
<meta name="robots" content="noindex">
24+
<meta name="Gitpod" content="Always Ready-to-Code" />
25+
<link rel="apple-touch-icon" href="/favicon192.png" />
26+
<link rel="manifest" href="/manifest.json" />
27+
<title>Dashboard</title>
28+
<script defer="defer" src="/static/js/main.b009793d.js"></script>
29+
<link href="/static/css/main.32e61b25.css" rel="stylesheet">
30+
</head>
31+
32+
<body><noscript>You need to enable JavaScript to run this app.</noscript>
33+
<div id="root"></div>
34+
</body>
35+
36+
</html>`
37+
38+
func Test_MatchAndRewriteRootRequest(t *testing.T) {
39+
40+
type Test struct {
41+
name string
42+
response *http.Response
43+
newBaseUrl string
44+
expectedBody string
45+
}
46+
tests := []Test{
47+
{
48+
name: "should match and rewrite root request",
49+
response: &http.Response{
50+
StatusCode: 200,
51+
Header: http.Header{
52+
"Content-Type": []string{"text/html"},
53+
},
54+
Body: ioutil.NopCloser(strings.NewReader(index_html)),
55+
},
56+
newBaseUrl: "https://3000-gitpodio-gitpod-hk3453q4csi.ws-eu108.gitpod.io",
57+
expectedBody: `<!doctype html>
58+
<html lang="en">
59+
60+
<head>
61+
<meta charset="utf-8" />
62+
<link rel="icon" href="https://3000-gitpodio-gitpod-hk3453q4csi.ws-eu108.gitpod.io/favicon256.png" />
63+
<meta name="viewport" content="width=device-width,initial-scale=1" />
64+
<meta name="theme-color" content="#000000" />
65+
<meta name="robots" content="noindex">
66+
<meta name="Gitpod" content="Always Ready-to-Code" />
67+
<link rel="apple-touch-icon" href="https://3000-gitpodio-gitpod-hk3453q4csi.ws-eu108.gitpod.io/favicon192.png" />
68+
<link rel="manifest" href="https://3000-gitpodio-gitpod-hk3453q4csi.ws-eu108.gitpod.io/manifest.json" />
69+
<title>Dashboard</title>
70+
<script defer="defer" src="https://3000-gitpodio-gitpod-hk3453q4csi.ws-eu108.gitpod.io/static/js/main.js"></script>
71+
72+
</head>
73+
74+
<body><noscript>You need to enable JavaScript to run this app.</noscript>
75+
<div id="root"></div>
76+
</body>
77+
78+
</html>`,
79+
},
80+
}
81+
82+
for _, test := range tests {
83+
t.Run(test.name, func(t *testing.T) {
84+
newBase, err := url.Parse(test.newBaseUrl)
85+
if err != nil {
86+
t.Errorf("error parsing new base url: %v", err)
87+
}
88+
actual := MatchAndRewriteRootRequest(test.response, newBase)
89+
actualBodyBytes, err := ioutil.ReadAll(actual.Body)
90+
if err != nil {
91+
t.Errorf("error reading response body: %v", err)
92+
}
93+
actualBody := string(actualBodyBytes)
94+
if strings.Compare(actualBody, test.expectedBody) != 0 {
95+
t.Errorf("got %v, want %v", actualBody, test.expectedBody)
96+
}
97+
})
98+
}
99+
}

components/proxy/plugins/frontend_dev/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module github.com/gitpod-io/gitpod/proxy/plugins/workspacedownload
1+
module github.com/gitpod-io/gitpod/proxy/plugins/frontend_dev
22

33
go 1.21
44

0 commit comments

Comments
 (0)