英文:
Serving react static files in golang gin-gonic using go:embed giving 404 error on reloading on frontend URL
问题
我已经使用gin和go1.17构建了一个Go应用程序。
我正在使用go:embed
来为使用React构建的SPA应用程序提供静态内容的服务。
(尝试按照https://github.com/gin-contrib/static/issues/19中建议的方法进行操作)。
我的前端文件位于build文件夹中。
build/index.html
build/asset-manifest.json
build/static/css/**
build/static/js/**
build/manifest.json
//go:embed build/*
var reactStatic embed.FS
type embedFileSystem struct {
http.FileSystem
indexes bool
}
func (e embedFileSystem) Exists(prefix string, path string) bool {
f, err := e.Open(path)
if err != nil {
return false
}
// check if indexing is allowed
s, _ := f.Stat()
if s.IsDir() && !e.indexes {
return false
}
return true
}
func EmbedFolder(fsEmbed embed.FS, targetPath string, index bool) static.ServeFileSystem {
subFS, err := fs.Sub(fsEmbed, targetPath)
if err != nil {
panic(err)
}
return embedFileSystem{
FileSystem: http.FS(subFS),
indexes: index,
}
}
func main() {
router := gin.Default()
fs := EmbedFolder(reactStatic, "build", true)
//Serve frontend static files
router.Use(static.Serve("/", fs))
/* THESE ARE MY STATIC URLs FROM THE REACT APP in FRONTEND */
router.Use(static.Serve("/login", fs))
router.Use(static.Serve("/calendar", fs))
router.NoRoute(func(c *gin.Context) {
c.JSON(404, gin.H{
"code": "PAGE_NOT_FOUND",
"message": "Page not found",
})
})
setupBaseRoutes(router, database)
httpServerExitDone := &sync.WaitGroup{}
httpServerExitDone.Add(1)
srv, ln := server.StartServer(router, httpServerExitDone)
log.Printf("Starting Server at %s", ln.Addr().String())
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
<-quit
log.Println("Shutdown Server ...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exiting")
}
当应用程序加载并打开页面http://localhost:8000/
时,它可以正常打开,并且我可以使用react-router-dom导航到http://localhost:8000/calendar
。
但是,当我重新加载页面http://localhost:8000/calendar
时,我会收到404错误。
英文:
I have built a go application using gin and go1.17.
I am using go:embed
to to serve static content for a SPA app built using react.
(trying the approach as suggested in https://github.com/gin-contrib/static/issues/19).
My frontend files are in a build folder
build/index.html
build/asset-manifest.json
build/static/css/**
build/static/js/**
build/manifest.json
//go:embed build/*
var reactStatic embed.FS
type embedFileSystem struct {
http.FileSystem
indexes bool
}
func (e embedFileSystem) Exists(prefix string, path string) bool {
f, err := e.Open(path)
if err != nil {
return false
}
// check if indexing is allowed
s, _ := f.Stat()
if s.IsDir() && !e.indexes {
return false
}
return true
}
func EmbedFolder(fsEmbed embed.FS, targetPath string, index bool) static.ServeFileSystem {
subFS, err := fs.Sub(fsEmbed, targetPath)
if err != nil {
panic(err)
}
return embedFileSystem{
FileSystem: http.FS(subFS),
indexes: index,
}
}
func main() {
router := gin.Default()
fs := EmbedFolder(reactStatic, "build", true)
//Serve frontend static files
router.Use(static.Serve("/", fs))
/* THESE ARE MY STATIC URLs FROM THE REACT APP in FRONTEND */
router.Use(static.Serve("/login", fs))
router.Use(static.Serve("/calendar", fs))
router.NoRoute(func(c *gin.Context) {
c.JSON(404, gin.H{
"code": "PAGE_NOT_FOUND", "message": "Page not found",
})
})
setupBaseRoutes(router, database)
httpServerExitDone := &sync.WaitGroup{}
httpServerExitDone.Add(1)
srv, ln := server.StartServer(router, httpServerExitDone)
log.Printf("Starting Server at %s", ln.Addr().String())
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
<-quit
log.Println("Shutdown Server ...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exiting")
}
When the application loads and the page http://localhost:8000/
is opened it opens properly and I can navigate to http://localhost:8000/calendar
using react-router-dom.
But when I reload the page http://localhost:8000/calendar
, I get 404 error.
答案1
得分: 3
我在那个github问题上发表了以下评论:
我成功地让我的SPA工作起来了,它从一个嵌入的_wwwroot_
目录提供服务,并在NoRoute
处理程序中进行了一个小小的修改,始终返回index.html
。我最初只是尝试这样做:
//go:embed wwwroot
var app embed.FS
wwwroot := embedFolder(app, "wwwroot")
router.Use(static.Serve("/", wwwroot))
router.NoRoute(func(c *gin.Context) {
c.FileFromFS("index.html", wwwroot)
})
但是这与http.serveFile函数的工作方式不兼容,当路径以/index.html
结尾时,它总是执行本地重定向到"/"
。所以我尝试了"index.html"
、""
、"/"
、"wwwroot"
和"wwwroot/"
,但是所有这些都失败了,因为在嵌入的文件系统中实际上没有这个文件。
我的解决方案是将请求的URL重写为空路径,并重新使用static.Serve
中间件,因为它可以通过手动调用来处理"/"
路径:
wwwroot := embedFolder(app, "wwwroot")
staticServer := static.Serve("/", wwwroot)
router.Use(staticServer)
router.NoRoute(func(c *gin.Context) {
if c.Request.Method == http.MethodGet &&
!strings.ContainsRune(c.Request.URL.Path, '.') &&
!strings.HasPrefix(c.Request.URL.Path, "/api/") {
c.Request.URL.Path = "/"
staticServer(c)
}
})
请注意,我只对不包含'.'
并且不以我的API前缀开头的GET
请求进行了这样的处理,所以对于API路由和不存在的文件(比如使用错误的图像路径)仍然会得到404
错误。
英文:
I made the following comment on that github issue:
I was able to get this working for my SPA, serving from a wwwroot embedded directory with a minor hack in the NoRoute
handler to always return index.html
. I was originally simply trying to do:
//go:embed wwwroot
var app embed.FS
wwwroot := embedFolder(app, "wwwroot")
router.Use(static.Serve("/", wwwroot))
router.NoRoute(func(c *gin.Context) {
c.FileFromFS("index.html", wwwroot)
})
but this doesn't play well with how the http.serveFile function always performs a local redirect to "/"
when the path ends with /index.html
. So instead of "index.html"
, I tried ""
, "/"
, "wwwroot"
, and "wwwroot/"
, but all of those failed because that wasn't actually a file in the embedded file system.
My solution was to re-write the request URL to the default empty path and re-use the static.Serve
middleware since it can handle the "/"
path by calling it manually:
wwwroot := embedFolder(app, "wwwroot")
staticServer := static.Serve("/", wwwroot)
router.Use(staticServer)
router.NoRoute(func(c *gin.Context) {
if c.Request.Method == http.MethodGet &&
!strings.ContainsRune(c.Request.URL.Path, '.') &&
!strings.HasPrefix(c.Request.URL.Path, "/api/") {
c.Request.URL.Path = "/"
staticServer(c)
}
})
Note that I'm only doing this for GET
requests that don't contain a '.'
or start with my API prefix so I should still get a 404
error for API routes and files that don't exist, like if I used a bad image path.
答案2
得分: 0
我成功找到了一个解决方法,将build/index.html
重命名为build/index.htm
。
由于某些原因,某个被gin使用的golang库中硬编码了index.html
,导致页面重新加载时出现404错误。
我在一个Github问题中读到了相关信息,但现在找不到那个问题的链接了。
英文:
I managed to find a workaround by renaming build/index.html
to build/index.htm
Due to some reason, index.html
is hard-coded in some golang library used by gin, which results in 404 on page reload.
I read about it in a Github issue but can't seem to find the link to that issue now.
答案3
得分: 0
也许你可以尝试这个,从这个代码库mandrigin/gin-spa进行改进。
package main
import (
"embed"
"io/fs"
"net/http"
"github.com/gin-gonic/contrib/static"
"github.com/gin-gonic/gin"
)
var (
//go:embed build
reactDir embed.FS
)
func EmbedReact(urlPrefix, buildDirectory string, em embed.FS) gin.HandlerFunc {
dir := static.LocalFile(buildDirectory, true)
embedDir, _ := fs.Sub(em, buildDirectory)
fileserver := http.FileServer(http.FS(embedDir))
if urlPrefix != "" {
fileserver = http.StripPrefix(urlPrefix, fileserver)
}
return func(c *gin.Context) {
if !dir.Exists(urlPrefix, c.Request.URL.Path) {
c.Request.URL.Path = "/"
}
fileserver.ServeHTTP(c.Writer, c.Request)
c.Abort()
}
}
func main() {
r := gin.Default()
r.Use(EmbedReact("/", "build", reactDir))
r.Run()
}
英文:
maybe you can try this bro, just improve from this repository mandrigin/gin-spa
package main
import (
"embed"
"io/fs"
"net/http"
"github.com/gin-gonic/contrib/static"
"github.com/gin-gonic/gin"
)
var (
//go:embed build
reactDir embed.FS
)
func EmbedReact(urlPrefix, buildDirectory string, em embed.FS) gin.HandlerFunc {
dir := static.LocalFile(buildDirectory, true)
embedDir, _ := fs.Sub(em, buildDirectory)
fileserver := http.FileServer(http.FS(embedDir))
if urlPrefix != "" {
fileserver = http.StripPrefix(urlPrefix, fileserver)
}
return func(c *gin.Context) {
if !dir.Exists(urlPrefix, c.Request.URL.Path) {
c.Request.URL.Path = "/"
}
fileserver.ServeHTTP(c.Writer, c.Request)
c.Abort()
}
}
func main() {
r := gin.Default()
r.Use(EmbedReact("/", "build", reactDir))
r.Run()
}
答案4
得分: 0
我找到了另一个解决方案:
Go,Gin和React
engine := gin.Default()
engine.Use(static.Serve("/", static.LocalFile("./client/build", true)))
engine.NoRoute(func(c *gin.Context) {
c.File("./client/build/index.html")
})
err := engine.Run(":8080")
请注意,这是Go语言的代码片段,使用了Gin框架和React前端。它创建了一个Gin引擎并将静态文件服务设置为React应用程序的构建目录。如果没有匹配的路由,它将返回React应用程序的index.html文件。最后,它在端口8080上运行引擎。
英文:
I found another solution:
Go, Gin & React
engine := gin.Default()
engine.Use(static.Serve("/", static.LocalFile("./client/build", true)))
engine.NoRoute(func(c *gin.Context) {
c.File("./client/build/index.html")
})
err := engine.Run(":8080")
答案5
得分: 0
我找到了一个稍微简单一些的方法,对我来说有效:
//go:embed build
var reactStatic embed.FS
static, err := fs.Sub(reactStatic, "build")
if err != nil {
panic(err)
}
r := gin.Default()
r.StaticFS("/", http.FS(reactStatic))
英文:
I found a slightly simpler way that's working for me:
//go:embed build
var reactStatic embed.FS
static, err := fs.Sub(reactStatic, "build")
if err != nil {
panic(err)
}
r := gin.Default()
r.StaticFS("/", http.FS(reactStatic))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论