Serving react static files in golang gin-gonic using go:embed giving 404 error on reloading on frontend URL

huangapple go评论110阅读模式
英文:

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() &amp;&amp; !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, &quot;build&quot;, true)
//Serve frontend static files
router.Use(static.Serve(&quot;/&quot;, fs))
/* THESE ARE MY STATIC URLs FROM THE REACT APP in FRONTEND  */
router.Use(static.Serve(&quot;/login&quot;, fs))
router.Use(static.Serve(&quot;/calendar&quot;, fs))
router.NoRoute(func(c *gin.Context) {
c.JSON(404, gin.H{
&quot;code&quot;: &quot;PAGE_NOT_FOUND&quot;, &quot;message&quot;: &quot;Page not found&quot;,
})
})
setupBaseRoutes(router, database)
httpServerExitDone := &amp;sync.WaitGroup{}
httpServerExitDone.Add(1)
srv, ln := server.StartServer(router, httpServerExitDone)
log.Printf(&quot;Starting Server at %s&quot;, ln.Addr().String())
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
&lt;-quit
log.Println(&quot;Shutdown Server ...&quot;)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal(&quot;Server Shutdown:&quot;, err)
}
log.Println(&quot;Server exiting&quot;)
}

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, &quot;wwwroot&quot;)

router.Use(static.Serve(&quot;/&quot;, wwwroot))
router.NoRoute(func(c *gin.Context) {
    c.FileFromFS(&quot;index.html&quot;, wwwroot)
})

but this doesn't play well with how the http.serveFile function always performs a local redirect to &quot;/&quot; when the path ends with /index.html. So instead of &quot;index.html&quot;, I tried &quot;&quot;, &quot;/&quot;, &quot;wwwroot&quot;, and &quot;wwwroot/&quot;, 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 &quot;/&quot; path by calling it manually:

wwwroot := embedFolder(app, &quot;wwwroot&quot;)
staticServer := static.Serve(&quot;/&quot;, wwwroot)

router.Use(staticServer)
router.NoRoute(func(c *gin.Context) {
    if c.Request.Method == http.MethodGet &amp;&amp;
        !strings.ContainsRune(c.Request.URL.Path, &#39;.&#39;) &amp;&amp;
        !strings.HasPrefix(c.Request.URL.Path, &quot;/api/&quot;) {
        c.Request.URL.Path = &quot;/&quot;
        staticServer(c)
    }
})

Note that I'm only doing this for GET requests that don't contain a &#39;.&#39; 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 (
&quot;embed&quot;
&quot;io/fs&quot;
&quot;net/http&quot;
&quot;github.com/gin-gonic/contrib/static&quot;
&quot;github.com/gin-gonic/gin&quot;
)
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 != &quot;&quot; {
fileserver = http.StripPrefix(urlPrefix, fileserver)
}
return func(c *gin.Context) {
if !dir.Exists(urlPrefix, c.Request.URL.Path) {
c.Request.URL.Path = &quot;/&quot;
}
fileserver.ServeHTTP(c.Writer, c.Request)
c.Abort()
}
}
func main() {
r := gin.Default()
r.Use(EmbedReact(&quot;/&quot;, &quot;build&quot;, 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(&quot;/&quot;, static.LocalFile(&quot;./client/build&quot;, true)))
engine.NoRoute(func(c *gin.Context) {
c.File(&quot;./client/build/index.html&quot;)
})
err := engine.Run(&quot;:8080&quot;)

答案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, &quot;build&quot;)
if err != nil {
  panic(err)
}

r := gin.Default()
r.StaticFS(&quot;/&quot;, http.FS(reactStatic))

huangapple
  • 本文由 发表于 2021年10月6日 16:37:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/69462376.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定