feat: implement client-side status code handling
Some checks failed
Contributors / contributors (push) Waiting to run
Sync / sync-to-jihulab (push) Waiting to run
Run Tests / tests (macos) (push) Waiting to run
Run Tests / tests (ubuntu) (push) Waiting to run
Run Tests / tests (windows) (push) Waiting to run
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled

This commit is contained in:
naiba 2025-01-05 23:53:04 +08:00
parent 693db2adef
commit f6683adb70
2 changed files with 65 additions and 27 deletions

View File

@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"os" "os"
"path" "path"
"regexp"
"slices" "slices"
"strings" "strings"
@ -285,31 +286,10 @@ func getUid(c *gin.Context) uint64 {
return user.ID return user.ID
} }
type ginCustomWriter struct {
gin.ResponseWriter
customCode int
}
func newCustomWriter(c *gin.Context, code int) *ginCustomWriter {
return &ginCustomWriter{
ResponseWriter: c.Writer,
customCode: code,
}
}
func (w *ginCustomWriter) WriteHeader(code int) {
w.ResponseWriter.WriteHeader(w.customCode)
}
func fileWithCustomStatusCode(c *gin.Context, filepath string, customCode int) {
http.ServeFile(newCustomWriter(c, customCode), c.Request, filepath)
}
func fallbackToFrontend(frontendDist fs.FS) func(*gin.Context) { func fallbackToFrontend(frontendDist fs.FS) func(*gin.Context) {
checkLocalFileOrFs := func(c *gin.Context, fs fs.FS, path string, customStatusCode int) bool { checkLocalFileOrFs := func(c *gin.Context, fs fs.FS, path string, customStatusCode int) bool {
if _, err := os.Stat(path); err == nil { if _, err := os.Stat(path); err == nil {
fileWithCustomStatusCode(c, path, customStatusCode) http.ServeFile(utils.NewGinCustomWriter(c, customStatusCode), c.Request, path)
return true return true
} }
f, err := fs.Open(path) f, err := fs.Open(path)
@ -324,22 +304,61 @@ func fallbackToFrontend(frontendDist fs.FS) func(*gin.Context) {
if fileStat.IsDir() { if fileStat.IsDir() {
return false return false
} }
http.ServeContent(newCustomWriter(c, customStatusCode), c.Request, path, fileStat.ModTime(), f.(io.ReadSeeker)) http.ServeContent(utils.NewGinCustomWriter(c, customStatusCode), c.Request, path, fileStat.ModTime(), f.(io.ReadSeeker))
return true return true
} }
frontendPageUrlRegistry := []*regexp.Regexp{
// official user frontend
regexp.MustCompile(`^/$`),
regexp.MustCompile(`^/server/\d*$`),
// backend frontend
regexp.MustCompile(`^/dashboard/$`),
regexp.MustCompile(`^/dashboard/login$`),
regexp.MustCompile(`^/dashboard/service$`),
regexp.MustCompile(`^/dashboard/cron$`),
regexp.MustCompile(`^/dashboard/notification$`),
regexp.MustCompile(`^/dashboard/alert-rule$`),
regexp.MustCompile(`^/dashboard/ddns$`),
regexp.MustCompile(`^/dashboard/nat$`),
regexp.MustCompile(`^/dashboard/server-group$`),
regexp.MustCompile(`^/dashboard/notification-group$`),
regexp.MustCompile(`^/dashboard/profile$`),
regexp.MustCompile(`^/dashboard/settings$`),
regexp.MustCompile(`^/dashboard/settings/user$`),
regexp.MustCompile(`^/dashboard/settings/online-user$`),
regexp.MustCompile(`^/dashboard/settings/waf$`),
}
getFallbackStatusCode := func(path string) int {
for _, reg := range frontendPageUrlRegistry {
if reg.MatchString(path) {
return http.StatusOK
}
}
return http.StatusNotFound
}
return func(c *gin.Context) { return func(c *gin.Context) {
if strings.HasPrefix(c.Request.URL.Path, "/api") { if strings.HasPrefix(c.Request.URL.Path, "/api") {
c.JSON(http.StatusNotFound, newErrorResponse(errors.New("404 Not Found"))) c.JSON(http.StatusNotFound, newErrorResponse(errors.New("404 Not Found")))
return return
} }
// redirect for /dashboard to /dashboard/
if c.Request.URL.Path == "/dashboard" {
c.Redirect(http.StatusMovedPermanently, "/dashboard/")
return
}
fallbackStatusCode := getFallbackStatusCode(c.Request.URL.Path)
if strings.HasPrefix(c.Request.URL.Path, "/dashboard") { if strings.HasPrefix(c.Request.URL.Path, "/dashboard") {
stripPath := strings.TrimPrefix(c.Request.URL.Path, "/dashboard") stripPath := strings.TrimPrefix(c.Request.URL.Path, "/dashboard")
localFilePath := path.Join(singleton.Conf.AdminTemplate, stripPath) localFilePath := path.Join(singleton.Conf.AdminTemplate, stripPath)
statusCode := utils.IfOr(stripPath == "/", http.StatusOK, http.StatusNotFound)
if checkLocalFileOrFs(c, frontendDist, localFilePath, http.StatusOK) { if checkLocalFileOrFs(c, frontendDist, localFilePath, http.StatusOK) {
return return
} }
if !checkLocalFileOrFs(c, frontendDist, singleton.Conf.AdminTemplate+"/index.html", statusCode) { if !checkLocalFileOrFs(c, frontendDist, singleton.Conf.AdminTemplate+"/index.html", fallbackStatusCode) {
c.JSON(http.StatusNotFound, newErrorResponse(errors.New("404 Not Found"))) c.JSON(http.StatusNotFound, newErrorResponse(errors.New("404 Not Found")))
} }
return return
@ -348,8 +367,7 @@ func fallbackToFrontend(frontendDist fs.FS) func(*gin.Context) {
if checkLocalFileOrFs(c, frontendDist, localFilePath, http.StatusOK) { if checkLocalFileOrFs(c, frontendDist, localFilePath, http.StatusOK) {
return return
} }
statusCode := utils.IfOr(c.Request.URL.Path == "/", http.StatusOK, http.StatusNotFound) if !checkLocalFileOrFs(c, frontendDist, singleton.Conf.UserTemplate+"/index.html", fallbackStatusCode) {
if !checkLocalFileOrFs(c, frontendDist, singleton.Conf.UserTemplate+"/index.html", statusCode) {
c.JSON(http.StatusNotFound, newErrorResponse(errors.New("404 Not Found"))) c.JSON(http.StatusNotFound, newErrorResponse(errors.New("404 Not Found")))
} }
} }

View File

@ -0,0 +1,20 @@
package utils
import "github.com/gin-gonic/gin"
type GinCustomWriter struct {
gin.ResponseWriter
customCode int
}
func NewGinCustomWriter(c *gin.Context, code int) *GinCustomWriter {
return &GinCustomWriter{
ResponseWriter: c.Writer,
customCode: code,
}
}
func (w *GinCustomWriter) WriteHeader(code int) {
w.ResponseWriter.WriteHeader(w.customCode)
}