Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix error in createStaticHandler when handling directories and index.html #4073

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

aleimu
Copy link

@aleimu aleimu commented Oct 12, 2024

My front-end project files and example code are as follows. I want to embed static files into the binary package

➜  gin_test ll -R static  
static:
total 8.0K
drwxr-xr-x 3 test test 4.0K Oct 12 14:28 dist
-rw-r--r-- 1 test test   66 Oct 12 14:40 dist.go

static/dist:
total 12K
drwxr-xr-x 3 test test 4.0K Oct 12 14:59 assets
-rw-r--r-- 1 test test 3.3K Oct 12 10:55 favicon.png
-rw-r--r-- 1 test test  570 Oct 12 10:55 index.html

static/dist/assets:
total 20K
drwxr-xr-x 2 test test 20K Oct 12 14:59 js

static/dist/assets/js:
total 4.0K
-rw-r--r-- 1 test test 1014 Oct 12 10:55 my.js
package main

import (
	"embed"
	"net/http"

	"github.com/gin-gonic/gin"
)

//go:embed static/dist
var staticFiles embed.FS

func main() {
	r := gin.Default()
	r.StaticFS("/", http.FS(staticFiles))
	r.Run("127.0.0.1:8081")
}

However, I encountered a problem: the conflict between Gin's StaticFS and http/fs. This conflict causes the static file directory to be inaccessible. The phenomenon is as follows:

curl http://127.0.0.1:8081/static/dist/assets/js/ # 404
curl http://127.0.0.1:8081/static/dist/ # 404
curl http://127.0.0.1:8081/static/dist/index.html # 404
curl http://127.0.0.1:8081/static/dist/assets/js/my.js # 200
curl http://127.0.0.1:8081/static/dist/assets/js/ # 404

In gin-gonic/[email protected]/routergroup.go, the createStaticHandler function has logic to read files to detect whether they exist. However, in sdk/go1.21.1/src/net/http/fs.go, the createStaticHandler function not only has this logic but also checks if the file is a directory, and if so, it defaults to reading the index.html file inside the directory.

Due to the simplicity of the createStaticHandler function in Gin, the logic in fs.createStaticHandler is being prematurely terminated. I suggest removing the createStaticHandler function from Gin or rewriting it to mimic the behavior of fs.createStaticHandler. The problematic code is as follows:

// routergroup.go
func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc {
	absolutePath := group.calculateAbsolutePath(relativePath)
	fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))

	return func(c *Context) {
		if _, noListing := fs.(*onlyFilesFS); noListing {
			c.Writer.WriteHeader(http.StatusNotFound)
		}

		file := c.Param("filepath")
		// Check if file exists and/or if we have permission to access it
		f, err := fs.Open(file)
		if err != nil {
			f1, err := f.Stat()
			c.Writer.WriteHeader(http.StatusNotFound)
			c.handlers = group.engine.noRoute
			// Reset index
			c.index = -1
			return
		}
		f.Close()

		fileServer.ServeHTTP(c.Writer, c.Request)
	}
}
// fs.go
func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
	const indexPage = "/index.html"

	// redirect .../index.html to .../
	// can't use Redirect() because that would make the path absolute,
	// which would be a problem running under StripPrefix
	if strings.HasSuffix(r.URL.Path, indexPage) {
		localRedirect(w, r, "./")
		return
	}
	f, err := fs.Open(name)
	if err != nil {
		msg, code := toHTTPError(err)
		Error(w, msg, code)
		return
	}
	defer f.Close()

	d, err := f.Stat()
	if err != nil {
		msg, code := toHTTPError(err)
		Error(w, msg, code)
		return
	}

	if redirect {
		// redirect to canonical path: / at end of directory url
		// r.URL.Path always begins with /
		url := r.URL.Path
		if d.IsDir() {
			if url[len(url)-1] != '/' {
				localRedirect(w, r, path.Base(url)+"/")
				return
			}
		} else {
			if url[len(url)-1] == '/' {
				localRedirect(w, r, "../"+path.Base(url))
				return
			}
		}
	}

	if d.IsDir() {
		url := r.URL.Path
		// redirect if the directory name doesn't end in a slash
		if url == "" || url[len(url)-1] != '/' {
			localRedirect(w, r, path.Base(url)+"/")
			return
		}

		// use contents of index.html for directory, if present
		index := strings.TrimSuffix(name, "/") + indexPage
		ff, err := fs.Open(index)
		if err == nil {
			defer ff.Close()
			dd, err := ff.Stat()
			if err == nil {
				d = dd
				f = ff
			}
		}
	}
    ......

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant