mirror of
https://github.com/nezhahq/nezha.git
synced 2025-01-22 12:48:14 -05:00
improve: 404 status code
This commit is contained in:
parent
553f8e58d4
commit
828588eef1
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@ -48,7 +48,12 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "1.23.x"
|
go-version: "1.23.1"
|
||||||
|
|
||||||
|
- name: patch net/http/fs.go
|
||||||
|
run: |
|
||||||
|
patch -p0 --forward `go env GOROOT`/src/net/http/fs.go < ./script/patch/fs.patch || true
|
||||||
|
patch -p0 --forward `go env GOMODCACHE`/github.com/gin-gonic/gin@v1.10.0/context.go < ./script/patch/gin-context.patch || true
|
||||||
|
|
||||||
- name: generate swagger docs
|
- name: generate swagger docs
|
||||||
run: |
|
run: |
|
||||||
|
7
.github/workflows/test.yml
vendored
7
.github/workflows/test.yml
vendored
@ -27,7 +27,12 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "1.23.x"
|
go-version: "1.23.1"
|
||||||
|
|
||||||
|
- name: patch net/http/fs.go
|
||||||
|
run: |
|
||||||
|
patch -p0 --forward `go env GOROOT`/src/net/http/fs.go < ./script/patch/fs.patch || true
|
||||||
|
patch -p0 --forward `go env GOMODCACHE`/github.com/gin-gonic/gin@v1.10.0/context.go < ./script/patch/gin-context.patch || true
|
||||||
|
|
||||||
- name: generate swagger docs
|
- name: generate swagger docs
|
||||||
run: |
|
run: |
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/nezhahq/nezha/cmd/dashboard/controller/waf"
|
"github.com/nezhahq/nezha/cmd/dashboard/controller/waf"
|
||||||
docs "github.com/nezhahq/nezha/cmd/dashboard/docs"
|
docs "github.com/nezhahq/nezha/cmd/dashboard/docs"
|
||||||
"github.com/nezhahq/nezha/model"
|
"github.com/nezhahq/nezha/model"
|
||||||
|
"github.com/nezhahq/nezha/pkg/utils"
|
||||||
"github.com/nezhahq/nezha/service/singleton"
|
"github.com/nezhahq/nezha/service/singleton"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -281,9 +282,9 @@ func getUid(c *gin.Context) uint64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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) 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 {
|
||||||
c.File(path)
|
c.FileWithCustomStatusCode(path, customStatusCode)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
f, err := fs.Open(path)
|
f, err := fs.Open(path)
|
||||||
@ -298,7 +299,7 @@ func fallbackToFrontend(frontendDist fs.FS) func(*gin.Context) {
|
|||||||
if fileStat.IsDir() {
|
if fileStat.IsDir() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
http.ServeContent(c.Writer, c.Request, path, fileStat.ModTime(), f.(io.ReadSeeker))
|
http.ServeContentCustomStatusCode(c.Writer, c.Request, path, fileStat.ModTime(), f.(io.ReadSeeker), customStatusCode)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
@ -309,29 +310,21 @@ func fallbackToFrontend(frontendDist fs.FS) func(*gin.Context) {
|
|||||||
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)
|
||||||
if stripPath == "/" {
|
statusCode := utils.IfOr(stripPath == "/", http.StatusOK, http.StatusNotFound)
|
||||||
c.Status(http.StatusOK)
|
if checkLocalFileOrFs(c, frontendDist, localFilePath, http.StatusOK) {
|
||||||
}
|
|
||||||
if checkLocalFileOrFs(c, frontendDist, localFilePath) {
|
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
c.Status(http.StatusNotFound)
|
|
||||||
}
|
}
|
||||||
if !checkLocalFileOrFs(c, frontendDist, singleton.Conf.AdminTemplate+"/index.html") {
|
if !checkLocalFileOrFs(c, frontendDist, singleton.Conf.AdminTemplate+"/index.html", statusCode) {
|
||||||
c.JSON(http.StatusNotFound, newErrorResponse(errors.New("404 Not Found")))
|
c.JSON(http.StatusNotFound, newErrorResponse(errors.New("404 Not Found")))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
localFilePath := path.Join(singleton.Conf.UserTemplate, c.Request.URL.Path)
|
localFilePath := path.Join(singleton.Conf.UserTemplate, c.Request.URL.Path)
|
||||||
if c.Request.URL.Path == "/" {
|
if checkLocalFileOrFs(c, frontendDist, localFilePath, http.StatusOK) {
|
||||||
c.Status(http.StatusOK)
|
|
||||||
}
|
|
||||||
if checkLocalFileOrFs(c, frontendDist, localFilePath) {
|
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
c.Status(http.StatusNotFound)
|
|
||||||
}
|
}
|
||||||
if !checkLocalFileOrFs(c, frontendDist, singleton.Conf.UserTemplate+"/index.html") {
|
statusCode := utils.IfOr(c.Request.URL.Path == "/", http.StatusOK, http.StatusNotFound)
|
||||||
|
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")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
102
script/patch/fs.patch
Normal file
102
script/patch/fs.patch
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
@@ -249,9 +249,24 @@
|
||||||
|
}
|
||||||
|
return size, nil
|
||||||
|
}
|
||||||
|
- serveContent(w, req, name, modtime, sizeFunc, content)
|
||||||
|
+ serveContent(w, req, name, modtime, sizeFunc, content, StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
+func ServeContentCustomStatusCode(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker, code int) {
|
||||||
|
+ sizeFunc := func() (int64, error) {
|
||||||
|
+ size, err := content.Seek(0, io.SeekEnd)
|
||||||
|
+ if err != nil {
|
||||||
|
+ return 0, errSeeker
|
||||||
|
+ }
|
||||||
|
+ _, err = content.Seek(0, io.SeekStart)
|
||||||
|
+ if err != nil {
|
||||||
|
+ return 0, errSeeker
|
||||||
|
+ }
|
||||||
|
+ return size, nil
|
||||||
|
+ }
|
||||||
|
+ serveContent(w, req, name, modtime, sizeFunc, content, code)
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
// errSeeker is returned by ServeContent's sizeFunc when the content
|
||||||
|
// doesn't seek properly. The underlying Seeker's error text isn't
|
||||||
|
// included in the sizeFunc reply so it's not sent over HTTP to end
|
||||||
|
@@ -266,15 +281,13 @@
|
||||||
|
// if modtime.IsZero(), modtime is unknown.
|
||||||
|
// content must be seeked to the beginning of the file.
|
||||||
|
// The sizeFunc is called at most once. Its error, if any, is sent in the HTTP response.
|
||||||
|
-func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, sizeFunc func() (int64, error), content io.ReadSeeker) {
|
||||||
|
+func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, sizeFunc func() (int64, error), content io.ReadSeeker, code int) {
|
||||||
|
setLastModified(w, modtime)
|
||||||
|
done, rangeReq := checkPreconditions(w, r, modtime)
|
||||||
|
if done {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
- code := StatusOK
|
||||||
|
-
|
||||||
|
// If Content-Type isn't set, use the file's extension to find it, but
|
||||||
|
// if the Content-Type is unset explicitly, do not sniff the type.
|
||||||
|
ctypes, haveType := w.Header()["Content-Type"]
|
||||||
|
@@ -671,7 +684,7 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
// name is '/'-separated, not filepath.Separator.
|
||||||
|
-func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
|
||||||
|
+func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool, statusCode int) {
|
||||||
|
const indexPage = "/index.html"
|
||||||
|
|
||||||
|
// redirect .../index.html to .../
|
||||||
|
@@ -753,7 +766,7 @@
|
||||||
|
|
||||||
|
// serveContent will check modification time
|
||||||
|
sizeFunc := func() (int64, error) { return d.Size(), nil }
|
||||||
|
- serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f)
|
||||||
|
+ serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f, statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// toHTTPError returns a non-specific HTTP error message and status code
|
||||||
|
@@ -814,7 +827,21 @@
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dir, file := filepath.Split(name)
|
||||||
|
- serveFile(w, r, Dir(dir), file, false)
|
||||||
|
+ serveFile(w, r, Dir(dir), file, false, StatusOK)
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func ServeFileWithCustomStatusCode(w ResponseWriter, r *Request, name string, statusCode int) {
|
||||||
|
+ if containsDotDot(r.URL.Path) {
|
||||||
|
+ // Too many programs use r.URL.Path to construct the argument to
|
||||||
|
+ // serveFile. Reject the request under the assumption that happened
|
||||||
|
+ // here and ".." may not be wanted.
|
||||||
|
+ // Note that name might not contain "..", for example if code (still
|
||||||
|
+ // incorrectly) used filepath.Join(myDir, r.URL.Path).
|
||||||
|
+ serveError(w, "invalid URL path", StatusBadRequest)
|
||||||
|
+ return
|
||||||
|
+ }
|
||||||
|
+ dir, file := filepath.Split(name)
|
||||||
|
+ serveFile(w, r, Dir(dir), file, false, statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeFileFS replies to the request with the contents
|
||||||
|
@@ -847,7 +874,7 @@
|
||||||
|
serveError(w, "invalid URL path", StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
- serveFile(w, r, FS(fsys), name, false)
|
||||||
|
+ serveFile(w, r, FS(fsys), name, false, StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsDotDot(v string) bool {
|
||||||
|
@@ -983,7 +1010,7 @@
|
||||||
|
upath = "/" + upath
|
||||||
|
r.URL.Path = upath
|
||||||
|
}
|
||||||
|
- serveFile(w, r, f.root, path.Clean(upath), true)
|
||||||
|
+ serveFile(w, r, f.root, path.Clean(upath), true, StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
// httpRange specifies the byte range to be sent to the client.
|
11
script/patch/gin-context.patch
Normal file
11
script/patch/gin-context.patch
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
@@ -1073,6 +1073,10 @@
|
||||||
|
// File writes the specified file into the body stream in an efficient way.
|
||||||
|
func (c *Context) File(filepath string) {
|
||||||
|
http.ServeFile(c.Writer, c.Request, filepath)
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func (c *Context) FileWithCustomStatusCode(filepath string, statusCode int) {
|
||||||
|
+ http.ServeFileWithCustomStatusCode(c.Writer, c.Request, filepath, statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileFromFS writes the specified file from http.FileSystem into the body stream in an efficient way.
|
Loading…
Reference in New Issue
Block a user