以下是有问题的实验代码。
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 3 天前
试一下 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 3 天前 via Android
HandleContext 内部转发
|
5
zzhirong OP @lovelylain 什么意思?你的意思是 /dist/index.html 会内部转发到 / 么?但是,wget 确实收到了 301 ,然后一直有尝试重定向,然后一直收到 301 。
|
6
kingcanfish 3 天前
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 2 天前
@zzhirong #7 最佳实践就是用 nginx 做🤣, gin 只用来跑 api
|
9
gvison 2 天前
我是这样使用 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 |