internal/router/ui.go (113 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 router import ( "embed" "fmt" "github.com/apache/answer/plugin" "io/fs" "net/http" "os" "strings" "github.com/apache/answer/internal/controller" "github.com/apache/answer/internal/service/siteinfo_common" "github.com/apache/answer/pkg/htmltext" "github.com/apache/answer/ui" "github.com/gin-gonic/gin" "github.com/segmentfault/pacman/log" ) const UIIndexFilePath = "build/index.html" const UIRootFilePath = "build" const UIStaticPath = "build/static" // UIRouter is an interface that provides ui static file routers type UIRouter struct { siteInfoController *controller.SiteInfoController siteInfoService siteinfo_common.SiteInfoCommonService } // NewUIRouter creates a new UIRouter instance with the embed resources func NewUIRouter( siteInfoController *controller.SiteInfoController, siteInfoService siteinfo_common.SiteInfoCommonService, ) *UIRouter { return &UIRouter{ siteInfoController: siteInfoController, siteInfoService: siteInfoService, } } // _resource is an interface that provides static file, it's a private interface type _resource struct { fs embed.FS } // Open to implement the interface by http.FS required func (r *_resource) Open(name string) (fs.File, error) { name = fmt.Sprintf(UIStaticPath+"/%s", name) log.Debugf("open static path %s", name) return r.fs.Open(name) } // Register a new static resource which generated by ui directory func (a *UIRouter) Register(r *gin.Engine, baseURLPath string) { staticPath := os.Getenv("ANSWER_STATIC_PATH") // if ANSWER_STATIC_PATH is set and not empty, ignore embed resource if staticPath != "" { info, err := os.Stat(staticPath) if err != nil || !info.IsDir() { log.Error(err) } else { log.Debugf("registering static path %s", staticPath) r.LoadHTMLGlob(staticPath + "/*.html") r.Static(baseURLPath+"/static", staticPath+"/static") r.NoRoute(func(c *gin.Context) { c.HTML(http.StatusOK, "index.html", gin.H{}) }) // return immediately if the static path is set return } } // handle the static file by default ui static files r.StaticFS(baseURLPath+"/static", http.FS(&_resource{ fs: ui.Build, })) // specify the not router for default routes and redirect r.NoRoute(func(c *gin.Context) { urlPath := c.Request.URL.Path if len(baseURLPath) > 0 { urlPath = strings.TrimPrefix(urlPath, baseURLPath) } filePath := "" switch urlPath { case "/favicon.ico": branding, err := a.siteInfoService.GetSiteBranding(c) if err != nil { log.Error(err) } if branding.Favicon != "" { c.String(http.StatusOK, htmltext.GetPicByUrl(branding.Favicon)) return } else if branding.SquareIcon != "" { c.String(http.StatusOK, htmltext.GetPicByUrl(branding.SquareIcon)) return } else { c.Header("content-type", "image/vnd.microsoft.icon") filePath = UIRootFilePath + urlPath } case "/manifest.json": a.siteInfoController.GetManifestJson(c) return case "/install": // if answer is running by run command user can not access install page. c.Redirect(http.StatusFound, "/") return default: filePath = UIIndexFilePath c.Header("content-type", "text/html;charset=utf-8") c.Header("X-Frame-Options", "DENY") } file, err := ui.Build.ReadFile(filePath) if err != nil { log.Error(err) c.Status(http.StatusNotFound) return } cdnPrefix := "" _ = plugin.CallCDN(func(fn plugin.CDN) error { cdnPrefix = fn.GetStaticPrefix() return nil }) if cdnPrefix != "" { if cdnPrefix[len(cdnPrefix)-1:] == "/" { cdnPrefix = strings.TrimSuffix(cdnPrefix, "/") } c.String(http.StatusOK, strings.ReplaceAll(string(file), "/static", cdnPrefix+"/static")) return } // This part is to solve the problem of returning 404 when the access path does not exist. // However, there is no way to check whether the current route exists in the frontend. // We can only hand over the route to the frontend for processing. // And the plugin, frontend routes can now be dynamically registered, // so there's no good way to get all frontend routes //if filePath == UIIndexFilePath { // c.String(http.StatusNotFound, string(file)) // return //} c.String(http.StatusOK, string(file)) }) }