Description

I've been serving my react build's index.html using router.Use(static.Serve("/", middlewares.EmbedFolder(publicDirectory, "public"))). But if I try to read the file in any other endpoint using ctx.FileFromFs, The request is being redirected to the filepath instead of being written to the body stream.

How to reproduce

main.go

package main

import (
        "embed"
        "io/fs"
    "github.com/gin-gonic/gin"
       "github.com/gin-gonic/contrib/static"
)

//go:embed public
var publicDirectory embed.FS

type EmbedFileSystem struct {
    http.FileSystem
}

func EmbedFolder(fsEmbed embed.FS, targetPath string) static.ServeFileSystem {
    fsys, err := fs.Sub(fsEmbed, targetPath)
    if err != nil {
        panic(err)
    }
    return EmbedFileSystem{
        FileSystem: http.FS(fsys),
    }
}

func main() {
    router := gin.Default()
    router.Use(middlewares.Cors())
    router.Use(static.Serve("/", EmbedFolder(publicDirectory, "public")))
        router.GET("/test",func(ctx *gin.Context) {
            ctx.FileFromFS("index.html",EmbedFolder(publicDirectory, "public"))
        })
    router.Run(":9000")
}

Folder structure

--gotest
     |--main.go
     |--public
          |--index.html 

Comment From: araujo88

What is the expected result and actual result?

Comment From: mari-muthu-k

What is the expected result and actual result?

Expected result : - written file content into the body stream of that request

Actual result : - Returned 301 and path to the file's location

Comment From: kaylee595

I don't know how the EmbedFileSystem structure implements the static.ServeFileSystem interface, according to my test as below, I didn't find a problem, hopefully this code snippet will help you. My guess is that if you are seeing a 301 status code in your browser's developer tools, this may be caused by the browser caching a permanent move to a new address, and you could try unit testing or clearing the browser cache.

Version - Golang: 1.20.2 - Gin: v1.9.1

Folder structure

|   go.mod
|   go.sum
|   main.go
|   main_test.go
|   static
    |   test.txt

static/test.txt

ok

main.go

const dirName = "static"

//go:embed static
var static embed.FS

func Router() *gin.Engine {
    engine := gin.Default()
    engine.Static("/static", "static")
    engine.GET("/success", func(c *gin.Context) {
        c.FileFromFS("static/test.txt", http.FS(static))
    })
    engine.GET("/fail", func(c *gin.Context) {
        //  Simulate 301 status code, used to test whether the unit test is automatically redirected, if the unit test can capture the 301 means that the unit test success is fine.
        c.Redirect(http.StatusMovedPermanently, "/static/test.txt")
    })
    return engine
}

func main() {

}

main_test.go

var engine = Router()

func TestGin(t *testing.T) {
    expected := "ok"
    t.Run("success", func(t *testing.T) {
        resp := request(httptest.NewRequest("GET", "/success", nil))
        assert.Equal(t, http.StatusOK, resp.StatusCode)
        assert.Equal(t, expected, string(must(io.ReadAll(resp.Body))))
    })
    t.Run("fail", func(t *testing.T) {
        resp := request(httptest.NewRequest("GET", "/fail", nil))
        assert.Equal(t, http.StatusMovedPermanently, resp.StatusCode)

        // Manually redirect to a new address to get the value
        resp = request(httptest.NewRequest("GET", resp.Header.Get("Location"), nil))
        assert.Equal(t, http.StatusOK, resp.StatusCode)
        assert.Equal(t, expected, string(must(io.ReadAll(resp.Body))))
    })
}

func request(req *http.Request) *http.Response {
    rr := httptest.NewRecorder()
    engine.ServeHTTP(rr, req)
    return rr.Result()
}

func must[T any](v T, err error) T {
    if err != nil {
        panic(err)
    }
    return v
}

Out

=== RUN   TestGin
=== RUN   TestGin/success
[GIN] 2023/08/18 - 01:25:54 | 200 |      26.147ms |       192.0.2.1 | GET      "/success"
=== RUN   TestGin/fail
[GIN] 2023/08/18 - 01:25:54 | 301 |            0s |       192.0.2.1 | GET      "/fail"
[GIN] 2023/08/18 - 01:25:54 | 200 |       502.2µs |       192.0.2.1 | GET      "/static/test.txt"
--- PASS: TestGin (0.03s)
    --- PASS: TestGin/success (0.03s)
    --- PASS: TestGin/fail (0.00s)
PASS