setup 404 NotFound handler on a gorilla mux router - go

Here is my code about a small demonstration webserver written with the Go language and the gorilla mux package :
package main
import (
"fmt"
"github.com/gorilla/mux"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
fmt.Fprintf(w, "Hi there, I love %s!", vars["username"])
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
errorHandler(w, r, http.StatusNotFound)
return
}
vars := mux.Vars(r)
fmt.Fprintf(w, "Hi there, I love %s!", vars["username"])
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/help/{username}/", handler)
http.Handle("/", r)
http.ListenAndServe(":8080", nil)
}
But I don't find a way on how to implement a custom 404 page.
But I can't make a r.HandleFunc("/",...) but it will be too greedy.

The Router exports a NotFoundHandler field which you can set to your custom handler.
r := mux.NewRouter()
r.NotFoundHandler = MyCustom404Handler

Sometimes, you spend a lot of time building a stack of middleware that does many things like logging, sending metrics and so... And the default 404 handler just skips all the middlewares.
I was able to solve this issue by re-setting the default 404 handler like this:
router := mux.NewRouter()
router.Use(someMiddleware())
// Re-define the default NotFound handler
router.NotFoundHandler = router.NewRoute().HandlerFunc(http.NotFound).GetHandler()
Now, the 404 default handler is also going thru all the middlewares.

Set the NotFoundHandler to a handler method that returns your custom 404 page.

r := mux.NewRouter()
h := http.HandlerFunc(NotFound)
r.NotFoundHandler = h
func NotFound(w http.ResponseWriter, r *http.Request) {
}

Related

GO gorilla/mux some pages serving html files return 404

I am working on this API in GO and im using gorilla/mux for my routing. THe home page with path "/" works fine, while routes like "/login" and "/register" return 404
package main
import (
"fmt"
"github.com/gorilla/mux"
"go-api/controller/ad"
"log"
"net/http"
"path"
)
func homePage(w http.ResponseWriter, r *http.Request) {
p := path.Dir("./public/views/index.html")
w.Header().Set("Content-type", "text/html")
http.ServeFile(w, r, p)
}
func showLogin(w http.ResponseWriter, r *http.Request) {
p := path.Dir("./public/views/login.html")
w.Header().Set("Content-type", "text/html")
http.ServeFile(w, r, p)
}
func showRegister(w http.ResponseWriter, r *http.Request) {
p := path.Dir("./public/views/register.html")
w.Header().Set("Content-type", "text/html")
http.ServeFile(w, r, p)
}
func main() {
baseUrl := "localhost:9010"
r := mux.NewRouter()
r.HandleFunc("/", homePage).Methods("GET")
r.HandleFunc("/ads", Ad.GetAds).Methods("GET")
r.HandleFunc("/login", showLogin).Methods("GET")
r.HandleFunc("/register", showRegister).Methods("GET")
http.Handle("/", r)
fmt.Printf("Server running at: %s", baseUrl)
log.Fatal(http.ListenAndServe(baseUrl, nil))
}
Yes, all 3 files exist in folder public/views:
index.html
login.html
register.html
What am I doing wrong? -How do I fix it so that the pages will show normally?
The cause is in the places where a directory is determined instead of the actual files to be used, e.g.
p := path.Dir("./public/views/login.html")
Go's documentation of Dir states:
Dir returns all but the last element of path, typically the path's directory.
But since you want to deliver the actual file, you should use the file path. The handler function would then look something like this:
func showLogin(w http.ResponseWriter, r *http.Request) {
p := "./public/views/login.html"
w.Header().Set("Content-type", "text/html")
http.ServeFile(w, r, p)
}
Note the removed path.Dir.
http://localhost:9010/login now serves the login.html file as expected.
(Generally, for testing, it is always good to clear the browsing data before testing so that no cached data is displayed.)

How to call a subpath endpoint in Go without call main path?

I'm having an issue with Golang using gorilla/mux.
I've declared an endpoint and certain subpath, but when I try to call the subpath BEFORE call it's main path, it returns me a 404 error.
So, there's my schema:
on main.go:
services.StartRestService()
on services package:
func StartRestService() {
log.Println("Loading Rest Service")
router := mux.NewRouter()
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { mainHandleRequests(w, r, router) }).Methods("GET")
http.Handle("/", router)
log.Fatal(http.ListenAndServe(":8083", router))
}
on another go file in the same services package:
func homePage(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome to the HomePage!")
fmt.Println("Endpoint Hit: homePage")
}
func pong(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Pong!")
fmt.Println("Endpoint Hit: pong")
}
func mainHandleRequests(w http.ResponseWriter, r *http.Request, router *mux.Router) {
homePage(w, r)
router.HandleFunc("/ping", pong)
}
Running my application, if I try to call http://localhost:8083/ping it returns me 404 not found. Only if I call http://localhost:8083, and after that I call the ping endpoint, it works.
What is wrong?
Yes, the router is unaware of the /ping endpoint until mainHandleRequests is called once.
So, the router should be set up and then not touched or changed in any way by incoming calls.
I have restructured your code a little:
func homePageHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome to the HomePage!")
fmt.Println("Endpoint Hit: homePage")
}
func pongHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Pong!")
fmt.Println("Endpoint Hit: pong")
}
func main() {
log.Println("Loading Rest Service")
router := mux.NewRouter()
router.Path("/").Methods(http.MethodGet).HandlerFunc(homePageHandler)
router.Path("/ping").Methods(http.MethodGet).HandlerFunc(pongHandler)
log.Fatal(http.ListenAndServe(":8083", router))
}

Does gorilla mux subrouters inherit the middlewares of their parent router?

The whole question is in the title.
I was searching on SO if a subrouter will use a middleware of its parent, in the case the middleware is applied to the parent router with the method Use(), but I couldn't find a clear concise answer.
I couldn't find that information in the package documentation either, so I decided to test it and post a question and an answer here for everyone in the same case.
In the following code sample, does requesting on /john will trigger the logMiddleware ?
mainRouter := mux.NewRouter()
mainRouter.Use(logMiddleware)
subRouter := mainRouter.PathPrefix("/users/").Subrouter()
subRouter.Handle("/john", johnHandler())
Yes, mux subrouters inherit their parent's middlewares when applied with Use() method.
Here is the test I created in case you want to try in your favorite IDE :
router code
package so
import (
"context"
"net/http"
"github.com/gorilla/mux"
)
func newRouter(useMainMiddleware bool) mux.Router {
mainRouter := mux.NewRouter()
if useMainMiddleware {
mainRouter.Use(middleware)
}
subRouter := mainRouter.PathPrefix("/users/").Subrouter()
subRouter.Handle("/test", testHandler())
return *mainRouter
}
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r = r.WithContext(context.WithValue(r.Context(), "is_using_middleware", true))
next.ServeHTTP(w, r)
})
}
func testHandler() http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
if using, castOk := r.Context().Value("is_using_middleware").(bool); castOk && using {
w.WriteHeader(http.StatusOK)
return
}
w.WriteHeader(http.StatusInternalServerError)
return
},
)
}
test file
package so
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSubrouterWithMiddleware(t *testing.T) {
// GIVEN
request := httptest.NewRequest(http.MethodGet, "/users/test", nil)
recorder := httptest.NewRecorder()
router := newRouter(true) // using a middleware
// WHEN
router.ServeHTTP(recorder, request)
// THEN
assert.Equal(t, http.StatusOK, recorder.Result().StatusCode)
}
func TestSubrouterWithoutMiddleware(t *testing.T) {
// GIVEN
request := httptest.NewRequest(http.MethodGet, "/users/test", nil)
recorder := httptest.NewRecorder()
router := newRouter(false) // not using a middleware
// WHEN
router.ServeHTTP(recorder, request)
// THEN
assert.Equal(t, http.StatusInternalServerError, recorder.Result().StatusCode)
}

Gorilla Mux not handling my path

When I use the default router from http everything works, but if I use the router from gorilla/mux instead, I get a 404 page with the body 404 page not found. As shown in the samples below, everything else is exactly the same.
Why doesn't the gorilla/mux router work like this?
Working correctly, using http routing:
package main
import "net/http"
func simplestPossible(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("MWE says OK"))
}
func main() {
http.HandleFunc("/", simplestPossible)
http.ListenAndServe(":8000", nil)
}
Not working, using gorilla/mux routing:
package main
import "net/http"
import "github.com/gorilla/mux"
func simplestPossible(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("MWE says OK"))
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/", simplestPossible)
http.ListenAndServe(":8000", nil)
}
You must pass your handler to http package (ListenAndServe):
http.ListenAndServe(":8000", r)

"Blank response" NotFoundHandler not working Gorilla

I am trying to debug 404-not-found by writing a custom not-found handler. Here is my code.
package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/coopernurse/gorp"
_ "github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
)
func main() {
// Create a MUX
r := mux.NewRouter()
http.Handle("/", r)
r.NotFoundHandler = http.HandlerFunc(NotFound)
// Static
r.PathPrefix("/app").HandlerFunc(uiAppHandler)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
}
func NotFound(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "custom 404")
}
func uiAppHandler(w http.ResponseWriter, r *http.Request) {
repoFrontend1 := "/UI/KD/WebContent"
http.StripPrefix("/app/", http.FileServer(http.Dir(repoFrontend1)))
}
I am getting a blank response for both existing and non-existing files. I guess NotFound is not getting triggered because of my "/" handler. Then how do I handle notFound for http.Dir?
Here is my directory structure
The response from uiAppHandler is blank because the function does not write to the response w. You should register the file server handler directly with the mux instead of trying to create a handler:
r.PathPrefix("/app").Handler(http.StripPrefix("/app/", http.FileServer(http.Dir(repoFrontend1))))
The mux passes all requests with the prefix "/app" to the handler registered for that prefix. All requests with that prefix are found as far as the mux is concerned. The http.FileServer or whatever you register for that prefix is responsible for generating the 404 response.

Resources