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
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")))