Hey! I am making an SPA and I need to fileserve on every page and also have an REST API.
I tried the fileserver example but I am getting the 404 page not found.
Using net/http works
package main
import (
"net/http"
"time"
)
func main() {
server := &http.Server{
Addr: ":8080",
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{ "message": "bar" }`))
})
http.Handle("/", http.FileServer(http.Dir("./web/dist")))
panic(server.ListenAndServe())
}
Using go-chi/chi not found
package main
import (
"net/http"
"time"
"github.com/go-chi/chi"
)
func main() {
router := chi.NewRouter()
server := &http.Server{
Addr: ":8080",
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
router.Get("/foo", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{ "message": "bar" }`))
})
// 404 Not Found
router.Handle("/", http.FileServer(http.Dir("./web/dist")))
panic(server.ListenAndServe())
}
Found the answer myself.
package cmd
import (
"net/http"
"os"
"time"
"github.com/go-chi/chi"
)
func main() {
router := chi.NewRouter()
server := &http.Server{
Addr: ":8080",
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
router.Get("/foo", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{ "message": "bar" }`))
})
FileServer(router)
panic(server.ListenAndServe())
}
// FileServer is serving static files.
func FileServer(router *chi.Mux) {
root := "./website/dist"
fs := http.FileServer(http.Dir(root))
router.Get("/*", func(w http.ResponseWriter, r *http.Request) {
if _, err := os.Stat(root + r.RequestURI); os.IsNotExist(err) {
http.StripPrefix(r.RequestURI, fs).ServeHTTP(w, r)
} else {
fs.ServeHTTP(w, r)
}
})
}
I've been struggling with this for days, thank you!
I adapted your snippet to allow SPA to be served from sub path. In vue cli its the publicPath config
package main
import (
"fmt"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/go-chi/chi"
)
const port = ":3333"
func main() {
fmt.Printf("Starting Server on Port %v\n", port)
router := chi.NewRouter()
server := &http.Server{
Addr: port,
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
router.Get("/foo", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{ "message": "bar" }`))
})
FileServer(router, "/admin", "dist/")
FileServer(router, "/", "../admin2/")
panic(server.ListenAndServe())
}
// FileServer is serving static files
func FileServer(r chi.Router, public string, static string) {
if strings.ContainsAny(public, "{}*") {
panic("FileServer does not permit URL parameters.")
}
root, _ := filepath.Abs(static)
if _, err := os.Stat(root); os.IsNotExist(err) {
panic("Static Documents Directory Not Found")
}
fs := http.StripPrefix(public, http.FileServer(http.Dir(root)))
if public != "/" && public[len(public)-1] != '/' {
r.Get(public, http.RedirectHandler(public+"/", 301).ServeHTTP)
public += "/"
}
r.Get(public+"*", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
file := strings.Replace(r.RequestURI, public, "/", 1)
if _, err := os.Stat(root + file); os.IsNotExist(err) {
http.ServeFile(w, r, path.Join(root, "index.html"))
return
}
fs.ServeHTTP(w, r)
}))
}
Hi @js02sixty
I have a question about these lines:
if strings.ContainsAny(public, "{}*") {
panic("FileServer does not permit URL parameters.")
}
Why does it need to check the "{}*"? Is it for security? I want to know the detail. Thanks.
Hi @js02sixty
I have a question about these lines:
if strings.ContainsAny(public, "{}*") { panic("FileServer does not permit URL parameters.") }Why does it need to check the "{}*"? Is it for security? I want to know the detail. Thanks.
Your probably right, i basically copied pkieltyka's example:
https://github.com/go-chi/chi/blob/master/_examples/fileserver/main.go
@TonyPythoneer @js02sixty hey guys -- the reason we cheque for any parameter characters such as {}* is because everything after the routing path that is passed to chi is handled by the handler in http.FileSystem. Therefore chi can't support variables for the folder path, and so we check and error out.
Most helpful comment
I adapted your snippet to allow SPA to be served from sub path. In vue cli its the publicPath config