// Copyright 2022 Harness Inc. All rights reserved. // Use of this source code is governed by the Polyform Free Trial License // that can be found in the LICENSE.md file for this repository. //go:build !proxy // +build !proxy // Package dist embeds the static web server content. package web import ( "bytes" "embed" "io" "io/fs" "net/http" "os" "path/filepath" "time" "github.com/rs/zerolog/log" ) //go:embed dist/* var UI embed.FS // Handler returns an http.HandlerFunc that servers the // static content from the embedded file system. func Handler() http.HandlerFunc { // Load the files subdirectory fs, err := fs.Sub(UI, "dist") 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) if filepath.Ext(r.URL.Path) == "" { // HACK: alter the path to point to the // root of the project. r.URL.Path = "/" } // 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" { 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) } } 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) } return remoteEntryContent, nil }