CODE-1845: Always serving index.html for static resources which are not found in dist folder (#2033)

pull/3519/head
Tan Nhu 2024-05-15 21:34:37 +00:00 committed by Harness
parent 49f3bf151e
commit 9480cf414c
1 changed files with 81 additions and 58 deletions

View File

@ -18,20 +18,23 @@ package web
import (
"bytes"
"embed"
"fmt"
"io"
"io/fs"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/rs/zerolog/log"
)
//go:embed dist/*
var UI embed.FS
var remoteEntryContent []byte
var fileMap map[string]bool
const distPath = "dist"
const remoteEntryJS = "remoteEntry.js"
const remoteEntryJSFullPath = "/" + remoteEntryJS
// Handler returns an http.HandlerFunc that servers the
// static content from the embedded file system.
@ -39,84 +42,104 @@ var UI embed.FS
//nolint:gocognit // refactor if required.
func Handler() http.HandlerFunc {
// Load the files subdirectory
fs, err := fs.Sub(UI, "dist")
fs, err := fs.Sub(UI, distPath)
if err != nil {
panic(err)
}
// Create an http.FileServer to serve the
// contents of the files subdiretory.
handler := http.FileServer(http.FS(fs))
// Create an http.HandlerFunc that wraps the
// http.FileServer to always load the index.html
// file if a directory path is being requested.
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// because this is a single page application,
// we need to always load the index.html file
// in the root of the project, unless the path
// points to a file with an extension (css, js, etc)
// No ext: (1) a browser URL request, not a static asset request
if filepath.Ext(r.URL.Path) == "" ||
// "..." : (2a) browser URL with ... in it
(strings.Contains(r.URL.Path, "...") &&
// (2b) filter out static asset URLs that browsers make along with it
filepath.Ext(strings.ReplaceAll(r.URL.Path, "...", "")) == "") {
// HACK: alter the path to point to the
// root of the project.
// Get the file base path
basePath := path.Base(r.URL.Path)
// If the file exists in dist/ then serve it from "/".
// Otherwise, rewrite the request to "/" so /index.html is served
if fileNotFoundInDist(basePath) {
r.URL.Path = "/"
} else {
// All static assets are served from the root path
r.URL.Path = "/" + path.Base(r.URL.Path)
r.URL.Path = "/" + basePath
}
// Disable caching and sniffing via HTTP headers for UI main entry resources
if r.URL.Path == "/" || r.URL.Path == "/remoteEntry.js" || r.URL.Path == "/index.html" {
if r.URL.Path == "/" || r.URL.Path == remoteEntryJSFullPath || r.URL.Path == "/index.html" {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate, max-age=0")
w.Header().Set("pragma", "no-cache")
w.Header().Set("X-Content-Type-Options", "nosniff")
}
if r.URL.Path == "/remoteEntry.js" {
if readerRemoteEntry, errFetch := fetchRemoteEntryJS(fs); errFetch == nil {
http.ServeContent(w, r, r.URL.Path, time.Now(), readerRemoteEntry)
} else {
log.Error().Msgf("Failed to fetch remoteEntry.js %v", err)
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
}
// Serve /remoteEntry.js from memory
if r.URL.Path == remoteEntryJSFullPath {
http.ServeContent(w, r, r.URL.Path, time.Now(), bytes.NewReader(remoteEntryContent))
} else {
// and finally serve the file.
handler.ServeHTTP(w, r)
}
})
}
var remoteEntryContent *bytes.Reader
func fetchRemoteEntryJS(fs fs.FS) (*bytes.Reader, error) {
if remoteEntryContent == nil {
path := "remoteEntry.js"
file, err := fs.Open(path)
if err != nil {
log.Error().Msgf("Failed to open file %v", path)
return nil, err
}
buf, err := io.ReadAll(file)
if err != nil {
log.Error().Msgf("Failed to read file %v", path)
return nil, err
}
enableCDN := os.Getenv("ENABLE_CDN")
if len(enableCDN) == 0 {
enableCDN = "false"
}
modBuf := bytes.Replace(buf, []byte("__ENABLE_CDN__"), []byte(enableCDN), 1)
remoteEntryContent = bytes.NewReader(modBuf)
func init() {
err := readRemoteEntryJSContent()
if err != nil {
panic(err)
}
return remoteEntryContent, nil
err = createFileMapForDistFolder()
if err != nil {
panic(err)
}
}
func readRemoteEntryJSContent() error {
fs, err := fs.Sub(UI, distPath)
if err != nil {
return fmt.Errorf("failed to open /dist: %w", err)
}
file, err := fs.Open(remoteEntryJS)
if err != nil {
return fmt.Errorf("failed to open remoteEntry.js: %w", err)
}
defer file.Close()
buf, err := io.ReadAll(file)
if err != nil {
return fmt.Errorf("failed to read remoteEntry.js: %w", err)
}
enableCDN := os.Getenv("ENABLE_CDN")
if len(enableCDN) == 0 {
enableCDN = "false"
}
remoteEntryContent = bytes.Replace(buf, []byte("__ENABLE_CDN__"), []byte(enableCDN), 1)
return nil
}
func createFileMapForDistFolder() error {
fileMap = make(map[string]bool)
err := fs.WalkDir(UI, distPath, func(path string, _ fs.DirEntry, err error) error {
if err != nil {
return fmt.Errorf("failed to build file map for path %q: %w", path, err)
}
if path != distPath { // exclude "dist" from file map
fileMap[path] = true
}
return nil
})
return err
}
func fileNotFoundInDist(path string) bool {
return !fileMap[distPath+"/"+path]
}