以下是有问题的实验代码。
package main /* 当前目录 . ├── go.mod ├── go.sum ├── dist │ └── index.html ├── main.go */ import ( "github.com/gin-gonic/gin" "embed" "net/http" ) //go:embed dist var fs embed.FS func main() { router := gin.Default() router.GET("/", func(c *gin.Context){ c.FileFromFS("dist/index.html", http.FS(fs)) }) router.Run(":8080") }
执行
go run . & wget http://localhost:8080
大家猜一下结果是什么?
问题出在
c.FileFromFS("dist/index.html", http.FS(fs))
c.FileFromFS
的第一个参数是以 “index.html”
结尾,c.FileFromFS
会调用net/http.FileServer
,而后者,在碰到以"index.html"
结尾的路径的时候,会返回 301,Location 设置成 ./
,然后,wget
在收到 301 后,又继续访问跳转地址 /
,然后就发生循环了。
解决方案就是:
index.html
改名(不推荐)以下是net/http.FileServer
的帮助文档:
go doc net/http.FileServer package http // import "net/http" func FileServer(root FileSystem) Handler FileServer returns a handler that serves HTTP requests with the contents of the file system rooted at root. As a special case, the returned file server redirects any request ending in "/index.html" to the same path, without the final "index.html". To use the operating system's file system implementation, use http.Dir: http.Handle("/", http.FileServer(http.Dir("/tmp"))) To use an fs.FS implementation, use http.FileServerFS instead.
这里有点违反直觉的是,c.HTML(200, "dist/index.html", nil)
和 c.File("dist/<只要不是index>.html")
是没问题的,但 c.File("dist/index.html")
就有问题,其实我后来想了下,FileServer
认为 /
和 /index.html
是等同的,所以也就不能把 / 映射成 /index.html ,但是我感觉这是一条隐性规则,单看 r.GET 或 c.File 的方法文档是无法知道的。
1 zzhirong OP 补充一点:帖子被贴上 “404 not found” 的标签了,应该是没有创建 ./dist/index.html 文件的缘故,但这里说的问题并不是这个低级错误。 - wget http://localhost:8080 返回的是 301 ,而且我这边返回的是 20 次 301 (超过阀值强行退出)。 |
![]() | 2 tbxark 203 天前 试一下 sf, err := fs.Sub(fs, "dist") |
3 zzhirong OP @tbxark 原因我昨天通过调试就已经知道了,就是先把问题代码贴上来,看看大家能否一眼就能看出,你提出的修改方案也还是会返回 301 // 还是会返回 301 dist, _ := fs.Sub(dist, "dist") router.GET("/", func(c *gin.Context){ c.FileFromFS("index.html", http.FS(dist)) }) |
4 lovelylain 203 天前 via Android HandleContext 内部转发 |
5 zzhirong OP @lovelylain 什么意思?你的意思是 /dist/index.html 会内部转发到 / 么?但是,wget 确实收到了 301 ,然后一直有尝试重定向,然后一直收到 301 。 |
6 kingcanfish 203 天前 package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { r := gin.Default() // 设置静态文件服务 r.Static("/dist", "./dist") // 根路径重定向到 /dist/index.html r.GET("/", func(c *gin.Context) { c.Redirect( http.StatusMovedPermanently, "/dist/index.html") }) // 启动服务器 r.Run(":8080") // 默认监听 0.0.0.0:8080 } 这样呗 你这样的用法确实感觉有点奇怪 ,属于是标准库没有想到用人会这么用 gin 也没想到有人会这么用 |
7 zzhirong OP @kingcanfish 你的方案会有两个 301 重定向,当你碰到首页不在根目录下的情况,你的最佳实践是什么,我目前的做法是 r.GET("/", func(c *gin.Context) { c.File("dist/") // 会直接返回 dist/index.html ,没有重定向 }) 把 / 映射到 index.html 应该很常见吧,有更好的方式么? |
8 kingcanfish 202 天前 @zzhirong #7 最佳实践就是用 nginx 做, gin 只用来跑 api |
9 gvison 202 天前 我是这样使用 gin 来做文件服务器获取 index.html ,访问 url 要求包括 go:embed 的目录 dist ,例如 wget http://localhost:8080/dist/index.html ```go package main import ( "embed" "github.com/gin-gonic/gin" "github.com/go-dev-frame/sponge/pkg/gin/frontend" ) //go:embed dist var staticFS embed.FS func main() { r := gin.Default() f := frontend.New("dist", frontend.WithEmbedFS(staticFS), frontend.With404ToHome(), ) err := f.SetRouter(r) if err != nil { panic(err) } err = r.Run(":8080") panic(err) } ``` |
11 zzhirong OP @gvison 看了下引用的 frontend 包的源码,发现 github.com/go-dev-frame/sponge/pkg/gin/frontend.FrontEnd.setEmbedFSRouter 有个新的实现方式 staticFS, _ := fs.Sub(staticFS, "dist") r.GET("/*file", func(c *gin.Context){ staticServer := http.FileServer( http.FS(staticFS)) staticServer.ServeHTTP(c.Writer, c.Request) }) 这种方案也是可以的,直接 wget http://localhost:8080/ 这会返回 301 |