Ctrl K
ComunidadGuías…

web/dist.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package dist embeds the static web server content.
package web

import (
	"bytes"
	"context"
	"embed"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"net/http"
	"os"
	"path"
	"time"

	"github.com/rs/zerolog/log"
)

//go:embed dist/*
var EmbeddedUIFS embed.FS

const (
	distPath              = "dist"
	remoteEntryJS         = "remoteEntry.js"
	remoteEntryJSFullPath = "/" + remoteEntryJS
)

// Handler returns an http.HandlerFunc that servers the
// static content from the embedded file system.
//
//nolint:gocognit // refactor if required.
func Handler(uiSourceOverride string) http.HandlerFunc {
	fs, err := fs.Sub(EmbeddedUIFS, distPath)
	if err != nil {
		panic(fmt.Errorf("failed to load embedded files: %w", err))
	}

	// override UI source if provided (for local development)
	if uiSourceOverride != "" {
		log.Info().Msgf("Starting with alternate UI located at %q", uiSourceOverride)
		fs = os.DirFS(uiSourceOverride)
	}

	remoteEntryContent, remoteEntryExists, err := readRemoteEntryJSContent(fs)
	if err != nil {
		panic(fmt.Errorf("failed to read remote entry JS content: %w", err))
	}

	fileMap, err := createFileMapForDistFolder(fs)
	if err != nil {
		panic(fmt.Errorf("failed to create file map for dist folder: %w", err))
	}

	publicIndexFileExists := fileMap["index_public.html"]

	// Create an http.FileServer to serve the contents of the files subdiretory.
	handler := http.FileServer(http.FS(fs))

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Get the file base path
		basePath := path.Base(r.URL.Path)

		// fallback to root in case the file doesn't exist so /index.html is served
		if !fileMap[basePath] {
			r.URL.Path = "/"
		} else {
			r.URL.Path = "/" + basePath
		}

		// handle public access
		if publicIndexFileExists &&
			RenderPublicAccessFrom(r.Context()) &&
			(r.URL.Path == "/" || r.URL.Path == "/index.html") {
			r.URL.Path = "/index_public.html"
		}

		const (
			maxAgeOneYear = 31536000 // 1 year in seconds
		)

		// Disable caching and sniffing via HTTP headers for UI main entry resources
		if r.URL.Path == "/" ||
			r.URL.Path == remoteEntryJSFullPath ||
			r.URL.Path == "/index.html" ||
			r.URL.Path == "/index_public.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")
		} else {
			// Apply long-term caching for static assets
			w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, immutable", maxAgeOneYear))
		}

		// Serve /remoteEntry.js from memory
		if remoteEntryExists && r.URL.Path == remoteEntryJSFullPath {
			http.ServeContent(w, r, r.URL.Path, time.Now(), bytes.NewReader(remoteEntryContent))
		} else {
			handler.ServeHTTP(w, r)
		}
	})
}

func readRemoteEntryJSContent(fileSystem fs.FS) ([]byte, bool, error) {
	file, err := fileSystem.Open(remoteEntryJS)
	if errors.Is(err, os.ErrNotExist) {
		return nil, false, nil
	}
	if err != nil {
		return nil, false, fmt.Errorf("failed to open remoteEntry.js: %w", err)
	}
	defer file.Close()

	buf, err := io.ReadAll(file)
	if err != nil {
		return nil, false, fmt.Errorf("failed to read remoteEntry.js: %w", err)
	}

	enableCDN := os.Getenv("ENABLE_CDN")

	if len(enableCDN) == 0 {
		enableCDN = "false"
	}

	return bytes.Replace(buf, []byte("__ENABLE_CDN__"), []byte(enableCDN), 1), true, nil
}

func createFileMapForDistFolder(fileSystem fs.FS) (map[string]bool, error) {
	fileMap := make(map[string]bool)

	err := fs.WalkDir(fileSystem, ".", func(path string, _ fs.DirEntry, err error) error {
		if err != nil {
			return fmt.Errorf("failed to read file info for %q: %w", path, err)
		}

		if path != "." {
			fileMap[path] = true
		}

		return nil
	})
	if err != nil {
		return nil, fmt.Errorf("failed to build file map: %w", err)
	}

	return fileMap, nil
}

// renderPublicAccessKey is the context key for storing and retrieving whether public access should be rendered.
type renderPublicAccessKey struct{}

// RenderPublicAccessFrom retrieves the public access rendering config from the context.
// If true, the UI should be rendered for public access.
func RenderPublicAccessFrom(ctx context.Context) bool {
	if v, ok := ctx.Value(renderPublicAccessKey{}).(bool); ok {
		return v
	}

	return false
}

// WithRenderPublicAccess returns a copy of parent in which the public access rendering is set to the provided value.
func WithRenderPublicAccess(parent context.Context, v bool) context.Context {
	return context.WithValue(parent, renderPublicAccessKey{}, v)
}