httpgogo-chi

Chi GoLang http.FileServer returning 404 page not found


I have this very simple code to serve some files:

    wd, _ := os.Getwd()
    fs := http.FileServer(http.Dir(filepath.Join(wd,"../", "static")))

    r.Handle("/static", fs)

But this is throwing a 404 error.

This directory is relative to my cmd/main.go, I also tried with it being relative to the current package, I also tried with os.Getwd(), and it didn't work. Note that I refer 'to not work' as 'not giving any error and returning 404 code'.

I expect that, when going to http://localhost:port/static/afile.png, the server will return this file, with the expected mime type.

This is my project structure:

- cmd
  main.go (main entry)
- static
  afile.png
- internal
  - routes
    static.go (this is where this code is being executed)
go.mod
go.sum

Note that I also tried using filepath.Join()

I Also tried other alternatives but they also gave a 404 error.

Edit: this is the os.Getwd() output from static.go:

/mnt/Files/Projects/backend/cmd (as expected)

This is the fmt.Println(filepath.Join(wd, "../", "static")) result /mnt/Files/Projects/backend/static

Minimal reproduction repository: https://github.com/dragonDScript/repro


Solution

  • Your first issue is: r.Handle("/static", fs). Handle is defined as func (mx *Mux) Handle(pattern string, handler http.Handler) where the docs describe pattern as:

    Each routing method accepts a URL pattern and chain of handlers. The URL pattern supports named params (ie. /users/{userID}) and wildcards (ie. /admin/). URL parameters can be fetched at runtime by calling chi.URLParam(r, "userID") for named parameters and chi.URLParam(r, "") for a wildcard parameter.

    So r.Handle("/static", fs) will match "/static" and only "/static". To match paths below this you would need to use r.Handle("/static/*", fs).

    The second issue is that you are requesting http://localhost:port/static/afile.png and this is served from /mnt/Files/Projects/backend/static meaning that the file the system is trying to load is /mnt/Files/Projects/backend/static/static/afile.png. A simple (but not ideal) way of solving this is to serve from the project root (fs := http.FileServer(http.Dir(filepath.Join(wd, "../")))). A better option is to use StripPrefix; either with a hardcoded prefix:

    fs := http.FileServer(http.Dir(filepath.Join(wd, "../", "static")))
    r.Handle("/static/*", http.StripPrefix("/static/",fs))
    

    Or the approach that the Chi sample code (note that the demo also adds a redirect for when the path is requested without specifying a specific file):

    fs := http.FileServer(http.Dir(filepath.Join(wd, "../", "static")))
        r.Get("/static/*", func(w http.ResponseWriter, r *http.Request) {
            rctx := chi.RouteContext(r.Context())
            pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*")
            fs := http.StripPrefix(pathPrefix, fs)
            fs.ServeHTTP(w, r)
        })
    

    Note: Using os.Getwd() has no benefit here; your application will access files relative to this path in any event so filepath.Join("../", "static")) is fine. If you want to make this relative to the path the executable is stored in (as opposed to the working directory) then you want something like:

    ex, err := os.Executable()
    if err != nil {
        panic(err)
    }
    exPath := filepath.Dir(ex)
    fs := http.FileServer(http.Dir(filepath.Join(exPath, "../static")))