有关 gin.Context.FileFromFS 的小坑 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
zzhirong
V2EX    Go 编程语言

有关 gin.Context.FileFromFS 的小坑

  •  
  •   zzhirong 203 天前 2090 次点击
    这是一个创建于 203 天前的主题,其中的信息可能已经有所发展或是发生改变。

    以下是有问题的实验代码。

    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 

    大家猜一下结果是什么?

    第 1 条附言    203 天前

    问题出在

    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 改名(不推荐)
    • 直接把"index.html"去掉(从"dist/index.html"改成`"dist/")

    以下是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. 
    第 2 条附言    202 天前

    这里有点违反直觉的是,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 的方法文档是无法知道的。

    11 条回复    2025-03-30 12:01:47 +08:00
    zzhirong
        1
    zzhirong  
    OP
       203 天前
    补充一点:帖子被贴上 “404 not found” 的标签了,应该是没有创建 ./dist/index.html 文件的缘故,但这里说的问题并不是这个低级错误。

    - wget http://localhost:8080 返回的是 301 ,而且我这边返回的是 20 次 301 (超过阀值强行退出)。
    tbxark
        2
    tbxark  
       203 天前
    试一下 sf, err := fs.Sub(fs, "dist")
    zzhirong
        3
    zzhirong  
    OP
       203 天前
    @tbxark 原因我昨天通过调试就已经知道了,就是先把问题代码贴上来,看看大家能否一眼就能看出,你提出的修改方案也还是会返回 301

    // 还是会返回 301
    dist, _ := fs.Sub(dist, "dist")
    router.GET("/", func(c *gin.Context){
    c.FileFromFS("index.html", http.FS(dist))
    })
    lovelylain
        4
    lovelylain  
       203 天前 via Android
    HandleContext 内部转发
    zzhirong
        5
    zzhirong  
    OP
       203 天前
    @lovelylain 什么意思?你的意思是 /dist/index.html 会内部转发到 / 么?但是,wget 确实收到了 301 ,然后一直有尝试重定向,然后一直收到 301 。
    kingcanfish
        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 也没想到有人会这么用
    zzhirong
        7
    zzhirong  
    OP
       203 天前
    @kingcanfish 你的方案会有两个 301 重定向,当你碰到首页不在根目录下的情况,你的最佳实践是什么,我目前的做法是

    r.GET("/", func(c *gin.Context) {
    c.File("dist/") // 会直接返回 dist/index.html ,没有重定向
    })

    把 / 映射到 index.html 应该很常见吧,有更好的方式么?
    kingcanfish
        8
    kingcanfish  
       202 天前
    @zzhirong #7 最佳实践就是用 nginx 做, gin 只用来跑 api
    gvison
        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)
    }
    ```
    zzhirong
        10
    zzhirong  
    OP
       202 天前
    @gvison 你的方案确实可以,但我不想引入额外的包而且我就想把主页挂在 / 下,有无办法只用 gin 实现?
    zzhirong
        11
    zzhirong  
    OP
       202 天前
    @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
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1020 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 18:45 PVG 02:45 LAX 11:45 JFK 14:45
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86