//go:build web package ext import ( "crypto/md5" "demo-server/pkg/str" "embed" "fmt" "github.com/gin-gonic/gin" "github.com/vearutop/statigz" "go.uber.org/zap" "io" "io/fs" "net/http" "strings" ) /* embed static web learn insp opensource license MIT see "github.com/v2rayA/v2rayA/server/router/index.go" */ func init() { initEngQueue = append(initEngQueue, initGUI) } // webRootFS 嵌入静态文件 // //go:embed web var webRootFS embed.FS func initGUI(r *gin.Engine) { webFS, err := fs.Sub(webRootFS, "web") if err != nil { zap.S().Panic("init gui err, cause: ", err) } guiStaticResource(r, webFS) guiIndexHTML(r, webFS) } // html 缓存 HTML 页面 func html(html []byte) func(ctx *gin.Context) { etag := fmt.Sprintf("W/%x", md5.Sum(html)) h := str.B2S(html) return func(ctx *gin.Context) { if ctx.IsAborted() { return } ctx.Header("Content-Type", "text/html; charset=utf-8") ctx.Header("Cache-Control", "public, must-revalidate") ctx.Header("ETag", etag) if match := ctx.GetHeader("If-None-Match"); match != "" { if strings.Contains(match, etag) { ctx.Status(http.StatusNotModified) return } } ctx.String(http.StatusOK, h) } } func guiStaticResource(r *gin.Engine, sfs fs.FS) { const ( assetsPrefix = "/assets" staticPrefix = "/static" stripPrefix = "" ) staticResource := http.StripPrefix(stripPrefix, statigz.FileServer(sfs.(fs.ReadDirFS))) r.GET(assetsPrefix+"/*w", func(c *gin.Context) { staticResource.ServeHTTP(c.Writer, c.Request) }) r.GET(staticPrefix+"/*w", func(c *gin.Context) { staticResource.ServeHTTP(c.Writer, c.Request) }) r.GET("/favicon.ico", func(c *gin.Context) { staticResource.ServeHTTP(c.Writer, c.Request) }) } func guiIndexHTML(r *gin.Engine, sfs fs.FS) { var indexHTML []byte { const indexFileName = "index.html" index, err := sfs.Open(indexFileName) if err != nil { zap.S().Panicf("open [%s] err! cause: %e", indexFileName, err) } indexHTML, err = io.ReadAll(index) if err != nil { zap.S().Panicf("read [%s] err! cause: %e", indexFileName, err) } } r.GET("/", html(indexHTML)) r.NoRoute(html(indexHTML)) }