Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NotFound ignored when FileServer using "/" path #155

Closed
jsadwith opened this issue Feb 13, 2017 · 7 comments
Closed

NotFound ignored when FileServer using "/" path #155

jsadwith opened this issue Feb 13, 2017 · 7 comments

Comments

@jsadwith
Copy link

jsadwith commented Feb 13, 2017

When using FileServer with a root ("/") path, the NotFound method seems to get ignored...

r := chi.NewRouter()

workDir, _ := os.Getwd()
filesDir := filepath.Join(workDir, "public")
r.FileServer("/", http.Dir(filesDir))

r.NotFound(func(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(404)
	w.Write([]byte("nothing here"))
})

An unknown route will always return the default "404 page not found" rather than the expected "nothing here". Changing the FileServer path to anything else (ie. "/foo") produces the expected "nothing here".

The goal is to be able to serve an Angular frontend at the root path, while all API routes are served via "/api/v1/". I also want to route any 404 traffic to the root to be able to better handle Angular routing if a user refreshes the page while within a frontend route.

Is the above an expected result or a bug? If expected, what would be the best way to go about doing what I'm looking for?

@pkieltyka
Copy link
Member

good question. I believe r.FileServer("/") will make a route on /* which catches every request to its handler. Potentially r.FileServer() impl could be changed to call r.NotFound() if a file is not found.

You do have the option to write the file serving handler yourself, the r.FileServer() is just a helper method. Please let me know if you solve it.

@jsadwith
Copy link
Author

@pkieltyka locally, I just updated the mx.Get() call in r.FileServer() to check to see if the file exists before serving. It may not be the most efficient solution, but it's working as it should...

mx.Get(path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	if _, err := os.Stat(fmt.Sprintf("%s", root) + r.RequestURI); os.IsNotExist(err) {
		mx.NotFoundHandler().ServeHTTP(w, r)
	} else {
		fs.ServeHTTP(w, r)
	}
}))

Any interest in a pull request for it? (I would include tests)

@pkieltyka
Copy link
Member

thanks for letting me know, but not at this time. But that should let you keep going, just take your own FileServer function to register the handler

@jsadwith
Copy link
Author

Cool. Here's my solution for a FileServer function that incorporates the pressly/chi NotFoundHandler for anyone that's interested...

workDir, _ := os.Getwd()
filesDir := filepath.Join(workDir, "public")
root := http.Dir(filesDir)
fs := http.StripPrefix("/", http.FileServer(root))
r.Get("/*", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	if _, err := os.Stat(fmt.Sprintf("%s", root) + r.RequestURI); os.IsNotExist(err) {
		r.NotFoundHandler().ServeHTTP(w, r)
	} else {
		fs.ServeHTTP(w, r)
	}
}))

Thanks @pkieltyka

@pkieltyka
Copy link
Member

thanks @jsadwith

@mitani
Copy link

mitani commented Apr 20, 2017

Just as a comment you would probably want to parse r.RequestURI as it will include the query string as well which you probably don't want when stating the file system

@andrewalexander
Copy link

andrewalexander commented Feb 25, 2021

Since this came up with trying out the embed.FS, we discovered this same error (nginx had handled it with a try_files $uri / type of thing before).

The above solution seems to basically be the same thing as the nginx rule and that worked for us when applied to the http.FS. It can be done with the embed.FS as well, but the method changes and is less portable since the http.FS can operate on both.

with a file layout of

app/
├── main.go
└── static
    ├── app.js
    ├── favicon.ico
    ├── fonts
    │   ├── Something.woff
    ├── index.html
    └── main.css

and the above, adapted:

//go:embed static/*
var content embed.FS
        
func main() {
	r := chi.NewRouter()
	newRoot, err := fs.Sub(content, "static")
	if err != nil {
		log.Fatal(err)
	}
	hfs := http.FS(newRoot)
	fserver := http.FileServer(hfs)
	r.Get("/*", func(w http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/" {
			if _, err := hfs.Open(strings.TrimPrefix(req.URL.Path, "/")); err != nil {
				http.Redirect(w, req, "/", http.StatusSeeOther)
				return
			}
		}
		fserver.ServeHTTP(w, req)
	})
	return http.ListenAndServe("localhost:8080", r)
}

you get a static webserver that doesn't 404 when a user tries to navigate to a view on the UI that the api server/web server isn't aware of.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants