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))
})
}