1

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

6
  • Can you add the request in your question? Commented Dec 26, 2020 at 1:13
  • 1
    What do you mean when you say "also tried with os.Getwd()" (os.Getwd returns the current working directory; adding the output to your question would be beneficial). As written the ../static will be relative to the current working directory (which may, or may not, be your cmd folder; it depends upon how you are running the application). Commented Dec 26, 2020 at 2:48
  • Seraf, what do you mean? Commented Dec 26, 2020 at 13:16
  • Thanks for the update - I assume you have the relevant files in /mnt/Files/Projects/backend/static. Seraf will be looking for the URL that you are requesting; it would be worth noting the full request (i.e. http://127.0.0.1/test.html) and the full path of the file that you expect this to return (after checking the file exists). We are looking for enough detail to be able to duplicate your issue (a Minimal, Reproducible Example would be great). Commented Dec 26, 2020 at 23:30
  • OK, I'm going to complete the information & make a repo. Thanks for your interest. Commented Dec 27, 2020 at 13:27

1 Answer 1

1

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")))
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.