Skip to content

Commit 8ca3822

Browse files
authored
Merge pull request #4027 from headlamp-k8s/fix_spa_handler
backend: Fix baseURL handling in embedded spa handler
2 parents d464d0e + 8804ad0 commit 8ca3822

File tree

2 files changed

+87
-35
lines changed

2 files changed

+87
-35
lines changed

backend/pkg/spa/embeddedSPAHandler.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,31 @@ func (h embeddedSpaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
3434
fullPath := filepath.Join("static", path)
3535

3636
content, err := h.serveFile(fullPath)
37+
isServingIndex := false
38+
3739
if err != nil {
3840
// If there's any error, serve the index file
3941
content, err = h.serveFile(filepath.Join("static", h.indexPath))
4042
if err != nil {
4143
http.Error(w, "Unable to read index file", http.StatusInternalServerError)
4244
return
4345
}
46+
47+
isServingIndex = true
48+
} else {
49+
// Check if we're directly serving the index file
50+
isServingIndex = path == h.indexPath || path == "/"+h.indexPath || path == "/"+h.indexPath+"/"
4451
}
4552

46-
// if request is for / or index.html replace the headlampBaseUrl with the baseURL in the index.html file
47-
if (path == h.indexPath || path == "/"+h.indexPath || path == "/"+h.indexPath+"/") && h.baseURL != "" {
48-
content = bytes.ReplaceAll(content, []byte("headlampBaseUrl = './"), []byte("headlampBaseUrl = '"+h.baseURL+""))
53+
// if we're serving the index.html file and have a baseURL, replace the headlampBaseUrl with the baseURL
54+
if h.baseURL != "" && isServingIndex {
55+
// Replace the __baseUrl__ assignment to use the baseURL instead of './'
56+
oldPattern := "__baseUrl__ = './<%= BASE_URL %>'.replace('%BASE_' + 'URL%', '').replace('<' + '%= BASE_URL %>', '');"
57+
newPattern := "__baseUrl__ = '" + h.baseURL + "';"
58+
content = bytes.ReplaceAll(content, []byte(oldPattern), []byte(newPattern))
59+
// Replace any remaining './' patterns in the content
4960
content = bytes.ReplaceAll(content, []byte("'./'"), []byte(h.baseURL+"/"))
61+
// Replace url( patterns for CSS
5062
content = bytes.ReplaceAll(content, []byte("url("), []byte("url("+h.baseURL+"/"))
5163
}
5264

backend/pkg/spa/embeddedSPAHandler_test.go

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,49 +18,89 @@ func createTestFS(files map[string]*fstest.MapFile) fs.FS {
1818
return fstest.MapFS(files)
1919
}
2020

21+
// getTestHTML returns the test HTML content used across tests.
22+
func getTestHTML() string {
23+
return `<!DOCTYPE html>
24+
<html lang="en">
25+
<head>
26+
<title>Headlamp</title>
27+
<script>
28+
// handles both webpack and rspack build systems
29+
__baseUrl__ = './<%= BASE_URL %>'.replace('%BASE_' + 'URL%', '').replace('<' + '%= BASE_URL %>', '');
30+
// the syntax of the following line is special - it will be matched and replaced by the server
31+
// if a base URL is set by the server
32+
headlampBaseUrl = __baseUrl__;
33+
</script>
34+
</head>
35+
<body>
36+
<div id="root"></div>
37+
</body>
38+
</html>`
39+
}
40+
2141
func TestEmbeddedSpaHandler(t *testing.T) {
22-
t.Run("check_headlampBaseUrl_is_set_to_baseURL", func(t *testing.T) {
23-
handler := spa.NewEmbeddedHandler(createTestFS(map[string]*fstest.MapFile{
24-
"static/index.html": {Data: []byte("headlampBaseUrl = './';")},
25-
}), "index.html", "/headlamp")
42+
testHTML := getTestHTML()
2643

27-
req, err := http.NewRequestWithContext(context.Background(), "GET", "/headlamp/index.html", nil)
28-
require.NoError(t, err)
44+
t.Run("check_headlampBaseUrl_is_set_to_baseURL", func(t *testing.T) {
45+
testHeadlampBaseURLWithBaseURL(t, testHTML)
46+
})
2947

30-
rr := httptest.NewRecorder()
31-
handler.ServeHTTP(rr, req)
48+
t.Run("check_empty_path_returns_index.html", func(t *testing.T) {
49+
testEmptyPathReturnsIndex(t, testHTML)
50+
})
3251

33-
assert.Equal(t, http.StatusOK, rr.Code)
34-
assert.Equal(t, "headlampBaseUrl = '/headlamp';", rr.Body.String())
52+
t.Run("file_not_found", func(t *testing.T) {
53+
testFileNotFound(t, testHTML)
3554
})
55+
}
3656

37-
t.Run("check_empty_path_returns_index.html", func(t *testing.T) {
38-
handler := spa.NewEmbeddedHandler(createTestFS(map[string]*fstest.MapFile{
39-
"static/index.html": {Data: []byte("headlampBaseUrl = './';")},
40-
}), "index.html", "/")
57+
func testHeadlampBaseURLWithBaseURL(t *testing.T, testHTML string) {
58+
handler := spa.NewEmbeddedHandler(createTestFS(map[string]*fstest.MapFile{
59+
"static/index.html": {Data: []byte(testHTML)},
60+
}), "index.html", "/headlamp")
4161

42-
req, err := http.NewRequestWithContext(context.Background(), "GET", "/headlamp/", nil)
43-
require.NoError(t, err)
62+
req, err := http.NewRequestWithContext(context.Background(), "GET", "/headlamp/index.html", nil)
63+
require.NoError(t, err)
4464

45-
rr := httptest.NewRecorder()
46-
handler.ServeHTTP(rr, req)
65+
rr := httptest.NewRecorder()
66+
handler.ServeHTTP(rr, req)
4767

48-
assert.Equal(t, http.StatusOK, rr.Code)
49-
assert.Equal(t, "headlampBaseUrl = './';", rr.Body.String())
50-
})
68+
assert.Equal(t, http.StatusOK, rr.Code)
69+
// Check that the __baseUrl__ assignment was replaced
70+
assert.Contains(t, rr.Body.String(), "__baseUrl__ = '/headlamp';")
71+
assert.Contains(t, rr.Body.String(), "headlampBaseUrl = __baseUrl__;")
72+
}
5173

52-
t.Run("file_not_found", func(t *testing.T) {
53-
handler := spa.NewEmbeddedHandler(createTestFS(map[string]*fstest.MapFile{
54-
"static/index.html": {Data: []byte("headlampBaseUrl = './';")},
55-
}), "index.html", "/headlamp")
74+
func testEmptyPathReturnsIndex(t *testing.T, testHTML string) {
75+
handler := spa.NewEmbeddedHandler(createTestFS(map[string]*fstest.MapFile{
76+
"static/index.html": {Data: []byte(testHTML)},
77+
}), "index.html", "/")
5678

57-
req, err := http.NewRequestWithContext(context.Background(), "GET", "/headlamp/not-found.html", nil)
58-
require.NoError(t, err)
79+
req, err := http.NewRequestWithContext(context.Background(), "GET", "/headlamp/", nil)
80+
require.NoError(t, err)
5981

60-
rr := httptest.NewRecorder()
61-
handler.ServeHTTP(rr, req)
82+
rr := httptest.NewRecorder()
83+
handler.ServeHTTP(rr, req)
6284

63-
assert.Equal(t, http.StatusOK, rr.Code)
64-
assert.Equal(t, "headlampBaseUrl = './';", rr.Body.String())
65-
})
85+
assert.Equal(t, http.StatusOK, rr.Code)
86+
// When baseURL is "/", the replacement should happen and __baseUrl__ should be set to "/"
87+
assert.Contains(t, rr.Body.String(), "__baseUrl__ = '/';")
88+
assert.Contains(t, rr.Body.String(), "headlampBaseUrl = __baseUrl__;")
89+
}
90+
91+
func testFileNotFound(t *testing.T, testHTML string) {
92+
handler := spa.NewEmbeddedHandler(createTestFS(map[string]*fstest.MapFile{
93+
"static/index.html": {Data: []byte(testHTML)},
94+
}), "index.html", "/headlamp")
95+
96+
req, err := http.NewRequestWithContext(context.Background(), "GET", "/headlamp/not-found.html", nil)
97+
require.NoError(t, err)
98+
99+
rr := httptest.NewRecorder()
100+
handler.ServeHTTP(rr, req)
101+
102+
assert.Equal(t, http.StatusOK, rr.Code)
103+
// Check that the __baseUrl__ assignment was replaced in fallback case
104+
assert.Contains(t, rr.Body.String(), "__baseUrl__ = '/headlamp';")
105+
assert.Contains(t, rr.Body.String(), "headlampBaseUrl = __baseUrl__;")
66106
}

0 commit comments

Comments
 (0)