mirror of
https://github.com/nezhahq/nezha.git
synced 2025-01-22 12:48:14 -05:00
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
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:
parent
693db2adef
commit
f6683adb70
@ -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")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
pkg/utils/gin_writer_wrapper.go
Normal file
20
pkg/utils/gin_writer_wrapper.go
Normal 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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user