Setting up Route Not Found in Gin - go

I've setup a default router and some routes in Gin:
router := gin.Default()
router.POST("/users", save)
router.GET("/users",getAll)
but how do I handle 404 Route Not Found in Gin?
Originally, I was using httprouter which I understand Gin uses so this was what I originally had...
router.NotFound = http.HandlerFunc(customNotFound)
and the function:
func customNotFound(w http.ResponseWriter, r *http.Request) {
//return JSON
return
}
but this won't work with Gin.
I need to be able to return JSON using the c *gin.Context so that I can use:
c.JSON(404, gin.H{"code": "PAGE_NOT_FOUND", "message": "Page not found"})

What you're looking for is the NoRoute handler.
More precisely:
r := gin.Default()
r.NoRoute(func(c *gin.Context) {
c.JSON(404, gin.H{"code": "PAGE_NOT_FOUND", "message": "Page not found"})
})

Adding to what Pablo Fernandez wrote I also seen that the same can be done with 405 MethodNotAllowed, so similarly if for 404 we've got NoRoute for 405 there is NoMethod method
So having this as result
import (
"github.com/gin-gonic/gin"
"log"
"net/http"
)
func main() {
app := gin.New()
app.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{"code": "PAGE_NOT_FOUND", "message": "404 page not found"})
})
app.NoMethod(func(c *gin.Context) {
c.JSON(http.StatusMethodNotAllowed, gin.H{"code": "METHOD_NOT_ALLOWED", "message": "405 method not allowed"})
})
}
In order to add a 'catch all' method we can do:
import (
"github.com/gin-gonic/gin"
"log"
"net/http"
)
func main() {
app := gin.New()
app.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{"code": "PAGE_NOT_FOUND", "message": "404 page not found"})
})
app.NoMethod(func(c *gin.Context) {
c.JSON(http.StatusMethodNotAllowed, gin.H{"code": "METHOD_NOT_ALLOWED", "message": "405 method not allowed"})
})
}
// Set-up Error-Handler Middleware
app.Use(func(c *gin.Context) {
log.Printf("Total Errors -> %d", len(c.Errors))
if len(c.Errors) <= 0 {
c.Next()
return
}
for _, err := range c.Errors {
log.Printf("Error -> %+v\n", err)
}
c.JSON(http.StatusInternalServerError, "")
})
Now, gin has these 2 method only for these 2 type of errors,my guess is because are the most common one while building an API and wanted to add some default handler when you first set up the application.
In fact, we can see the implementation here:
// NoRoute adds handlers for NoRoute. It returns a 404 code by default.
func NoRoute(handlers ...gin.HandlerFunc) {
engine().NoRoute(handlers...)
}
// NoMethod is a wrapper for Engine.NoMethod.
func NoMethod(handlers ...gin.HandlerFunc) {
engine().NoMethod(handlers...)
}
Now, the body that uses by default when these 2 handlers are not used by who uses the gin framework (so the default one are) are defined enter link description here
var (
default404Body = []byte("404 page not found")
default405Body = []byte("405 method not allowed")
)
And then later on used on the function handleHTTPRequest from line 632
if engine.HandleMethodNotAllowed {
for _, tree := range engine.trees {
if tree.method == httpMethod {
continue
}
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
c.handlers = engine.allNoMethod
serveError(c, http.StatusMethodNotAllowed, default405Body)
return
}
}
}
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}

Related

How to apply Chi middleware for subroutes?

Given the following sample API using Chi
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func main() {
http.ListenAndServe(":3000", GetRouter())
}
func GetRouter() *chi.Mux {
apiRouter := chi.NewRouter()
apiRouter.Route("/foo-group", func(fooGroupRouter chi.Router) {
fooGroupRouter.Use(middleware.AllowContentType("application/json"))
fooGroupRouter.Post("/sub-route", HandleRoute( /* Params */))
})
// other routes
return apiRouter
}
func HandleRoute( /* Params */) http.HandlerFunc {
return func(responseWriter http.ResponseWriter, request *http.Request) {
responseWriter.WriteHeader(http.StatusCreated)
responseWriter.Write([]byte("done"))
}
}
When calling the API via
POST localhost:3000/foo-group/sub-route
I get a 201 with "done". But I want to ensure this endpoint only accepts the content type "application/json", otherwise send back a 415.
Unfortunately the middleware is not working yet. I also tried to test the behaviour with the testrunner
package main
import (
"net/http"
"net/http/httptest"
"strconv"
"testing"
)
func TestHandleRoute(suite *testing.T) {
server := httptest.NewServer(HandleRoute())
suite.Run("responds with status code "+strconv.Itoa(http.StatusUnsupportedMediaType)+" if content type is not application/json", func(testing *testing.T) {
response, _ := http.Post(server.URL, "text/xml", nil)
if response.StatusCode != http.StatusUnsupportedMediaType {
testing.Errorf("Expected statuscode %d but got %d", http.StatusUnsupportedMediaType, response.StatusCode)
}
})
}
Unfortunately the test fails with the message
main_test.go:17: Expected statuscode 415 but got 201
so it seems the middleware didn't run. How can I fix that?

Why is the response body empty when running a test of mux API?

I am trying to build and test a very basic API in Go to learn more about the language after following their tutorial. The API and the four routes defined work in Postman and the browser, but when trying to write the test for any of the routes, the ResponseRecorder doesn't have a body, so I cannot verify it is correct.
I followed the example here and it works, but when I change it for my route, there is no response.
Here is my main.go file.
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
)
// A Person represents a user.
type Person struct {
ID string `json:"id,omitempty"`
Firstname string `json:"firstname,omitempty"`
Lastname string `json:"lastname,omitempty"`
Location *Location `json:"location,omitempty"`
}
// A Location represents a Person's location.
type Location struct {
City string `json:"city,omitempty"`
Country string `json:"country,omitempty"`
}
var people []Person
// GetPersonEndpoint returns an individual from the database.
func GetPersonEndpoint(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(req)
for _, item := range people {
if item.ID == params["id"] {
json.NewEncoder(w).Encode(item)
return
}
}
json.NewEncoder(w).Encode(&Person{})
}
// GetPeopleEndpoint returns all people from the database.
func GetPeopleEndpoint(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(people)
}
// CreatePersonEndpoint creates a new person in the database.
func CreatePersonEndpoint(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(req)
var person Person
_ = json.NewDecoder(req.Body).Decode(&person)
person.ID = params["id"]
people = append(people, person)
json.NewEncoder(w).Encode(people)
}
// DeletePersonEndpoint deletes a person from the database.
func DeletePersonEndpoint(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(req)
for index, item := range people {
if item.ID == params["id"] {
people = append(people[:index], people[index+1:]...)
break
}
}
json.NewEncoder(w).Encode(people)
}
// SeedData is just for this example and mimics a database in the 'people' variable.
func SeedData() {
people = append(people, Person{ID: "1", Firstname: "John", Lastname: "Smith", Location: &Location{City: "London", Country: "United Kingdom"}})
people = append(people, Person{ID: "2", Firstname: "John", Lastname: "Doe", Location: &Location{City: "New York", Country: "United States Of America"}})
}
func main() {
router := mux.NewRouter()
SeedData()
router.HandleFunc("/people", GetPeopleEndpoint).Methods("GET")
router.HandleFunc("/people/{id}", GetPersonEndpoint).Methods("GET")
router.HandleFunc("/people/{id}", CreatePersonEndpoint).Methods("POST")
router.HandleFunc("/people/{id}", DeletePersonEndpoint).Methods("DELETE")
fmt.Println("Listening on http://localhost:12345")
fmt.Println("Press 'CTRL + C' to stop server.")
log.Fatal(http.ListenAndServe(":12345", router))
}
Here is my main_test.go file.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
)
func TestGetPeopleEndpoint(t *testing.T) {
req, err := http.NewRequest("GET", "/people", nil)
if err != nil {
t.Fatal(err)
}
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
rr := httptest.NewRecorder()
handler := http.HandlerFunc(GetPeopleEndpoint)
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
// directly and pass in our Request and ResponseRecorder.
handler.ServeHTTP(rr, req)
// Trying to see here what is in the response.
fmt.Println(rr)
fmt.Println(rr.Body.String())
// Check the status code is what we expect.
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
// Check the response body is what we expect - Commented out because it will fail because there is no body at the moment.
// expected := `[{"id":"1","firstname":"John","lastname":"Smith","location":{"city":"London","country":"United Kingdom"}},{"id":"2","firstname":"John","lastname":"Doe","location":{"city":"New York","country":"United States Of America"}}]`
// if rr.Body.String() != expected {
// t.Errorf("handler returned unexpected body: got %v want %v",
// rr.Body.String(), expected)
// }
}
I appreciate that I am probably making a beginner mistake, so please take mercy on me. I have read a number of blogs testing mux, but can't see what I have done wrong.
Thanks in advance for your guidance.
UPDATE
Moving my SeeData call to init() resolved the body being empty for the people call.
func init() {
SeedData()
}
However, I now have no body returned when testing a specific id.
func TestGetPersonEndpoint(t *testing.T) {
id := 1
path := fmt.Sprintf("/people/%v", id)
req, err := http.NewRequest("GET", path, nil)
if err != nil {
t.Fatal(err)
}
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
rr := httptest.NewRecorder()
handler := http.HandlerFunc(GetPersonEndpoint)
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
// directly and pass in our Request and ResponseRecorder.
handler.ServeHTTP(rr, req)
// Check request is made correctly and responses.
fmt.Println(path)
fmt.Println(rr)
fmt.Println(req)
fmt.Println(handler)
// expected response for id 1.
expected := `{"id":"1","firstname":"John","lastname":"Smith","location":{"city":"London","country":"United Kingdom"}}` + "\n"
if status := rr.Code; status != http.StatusOK {
message := fmt.Sprintf("The test returned the wrong status code: got %v, but expected %v", status, http.StatusOK)
t.Fatal(message)
}
if rr.Body.String() != expected {
message := fmt.Sprintf("The test returned the wrong data:\nFound: %v\nExpected: %v", rr.Body.String(), expected)
t.Fatal(message)
}
}
Moving my SeedData call to init() resolved the body being empty for the people call.
func init() {
SeedData()
}
Creating a new router instance resolved the issue with accessing a variable on a route.
rr := httptest.NewRecorder()
router := mux.NewRouter()
router.HandleFunc("/people/{id}", GetPersonEndpoint)
router.ServeHTTP(rr, req)
I think its because your test isn't including the router hence the path variables aren't being detected. Here, try this
// main.go
func router() *mux.Router {
router := mux.NewRouter()
router.HandleFunc("/people", GetPeopleEndpoint).Methods("GET")
router.HandleFunc("/people/{id}", GetPersonEndpoint).Methods("GET")
router.HandleFunc("/people/{id}", CreatePersonEndpoint).Methods("POST")
router.HandleFunc("/people/{id}", DeletePersonEndpoint).Methods("DELETE")
return router
}
and in your testcase, initiatize from the router method like below
handler := router()
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
// directly and pass in our Request and ResponseRecorder.
handler.ServeHTTP(rr, req)
And now if you try accessing the path variable id, it should be present in the map retured by mux since mux registered it when you initiatlized the Handler from mux Router instance returned from router()
params := mux.Vars(req)
for index, item := range people {
if item.ID == params["id"] {
people = append(people[:index], people[index+1:]...)
break
}
}
Also like you mentioned, use the init function for one time setups.
// main.go
func init(){
SeedData()
}

Net/http Simple Dynamic Routes

I am looking for a simple way to create dynamic routes with net/http (no routers like mux etc.)
Here is my current code:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
pages := r.URL.Query()["q"]
if len(pages) == 0 {
fmt.Fprintf(w, "§§§§§§§§§§ You need to specify a page §§§§§§§§§§")
return
}
page := pages[0]
var a Page
err := db.QueryRow("SELECT * FROM pages where page = ?", page).Scan(&a.Page, &a.Date, &a.Url)
a.Year = time.Now().UTC().Year()
if err != nil {
if err == sql.ErrNoRows {
fmt.Fprintf(w, "Page %s not found", page)
return
} else {
fmt.Fprintf(w, "Some error happened")
return
}
}
http.Redirect(w, r, a.Url, 301)
})
So now the URL sample.com/?q= works dynamically.
My objective is to work without having to use r.URL.Query()["q"] so directly /pagename
This is not a duplicate of Go url parameters mapping because it is a single level (not nested levels) AND many answers in that question refer to using an external library.
If you don't want to use any third-party libraries, you have to handle the parsing of the path yourself.
For start, you can do this:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
page := r.URL.Path[1:]
// do whatever logic you want
// mind that the page could be "multi/level/path/" as well
})
You can use http.HandleFunc.
In this function, a pattern ending in a slash defines a subtree.
You can register a handler function with the pattern "/page/" like the below example.
package main
import (
"net/http"
"fmt"
)
func handler(w http.ResponseWriter, r *http.Request) {
if is_valid_page(r.URL) {
fmt.Fprint(w, "This is a valid page")
} else {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "Error 404 - Page not found")
}
}
func is_valid_page(page string) {
// check here if page is valid from url
}
func main() {
http.HandleFunc("/page/", handler)
http.ListenAndServe(":8080", nil)
}
more info you can find here: https://golang.org/pkg/net/http/#ServeMux

How to use http.ResponseWriter in fasthttp for executing HTML templates etc.?

I've recently moved from golang net/http to fasthttp owing to rave reviews.
As you know, fasthttp doesn't use (w http.ResponseWriter) but only one syntax which is (ctx *fasthttp.RequestCtx).
I tried using ctx.Write but it didn't work.
So, how can I implement http.ResponseWriter in the code below to excute my html templates? Please also give some explanation so that we could all benefit.
Much Thanks for your help!
package main()
import (
"html/template"
"fmt"
"github.com/valyala/fasthttp"
)
type PageData struct {
Title string
}
func init() {
tpl = template.Must(template.ParseGlob("public/templates/*.html"))
}
m := func(ctx *fasthttp.RequestCtx) {
switch string(ctx.Path()) {
case "/":
idx(ctx)
default:
ctx.Error("not found", fasthttp.StatusNotFound)
}
}
fasthttp.ListenAndServe(":8081", m)
}
func idx(ctx *fasthttp.RequestCtx) {
pd := new(PageData)
pd.Title = "Index Page"
err := tpl.ExecuteTemplate(ctx.write, "index.html", pd)
if err != nil {
log.Println("LOGGED", err)
http.Error(ctx.write, "Internal server error", http.StatusInternalServerError)
return
}
}
*fasthttp.RequestCtx implements the io.Writer interface (that's why ctx.Write() exists), which means that you can simply pass ctx as the parameter to ExecuteTemplate():
tpl.ExecuteTemplate(ctx, "index.html", pd)
Also, the http.Error() call will not work, since RequestCtx is not a http.ResponseWriter. Use the RequestCtx's own error function instead:
ctx.Error("Internal server error", http.StatusInternalServerError)

Runtime access to symbols in Go

In writing a Web server in Go, I'd like to be able to dereference symbols at runtime, to allow me to figure out which functions to call from a configuration file, something like the call to the fictional "eval" function in the example below. That would allow me to select handlers from a library of handlers, and to deploy a new server with just a config file. Is there any way to accomplish this in Go?
config.json
{ "url": "/api/apple", "handler": "Apple", "method": "get" }
{ "url": "/api/banana", "handler": "Banana", "method": "get" }
play.go
package main
import (
"github.com/gorilla/mux"
"net/http"
"encoding/json"
"log"
)
type ConfigEntry struct {
URL string `json:"url"`
Method string `json:"method"`
Handler string `json:"handler"`
}
func main() {
ifp, err := os.Open("config.json")
if err != nil {
log.Fatal(err)
}
dec := json.NewDecoder(ifp)
r := mux.NewRouter()
for {
var config ConfigEntry
if err = dec.Decode(&m); err == io.EOF {
break
} else if err != nil {
log.Fatal(err)
}
r.HandleFunc(config.URL, eval(config.Handler + "Handler")).Methods(config.Method)
}
http.Handle("/", r)
http.ListenAndServe(8080, nil)
}
func AppleHandler(w http.ResponseWriter, r *http.Request) (status int, err error) {
w.Write("Apples!\n")
return http.StatusOK, nil
}
func BananaHandler(w http.ResponseWriter, r *http.Request) (status int, err error) {
w.Write("Bananas!\n")
return http.StatusOK, nil
}
There's nothing like eval in Go, which is a good thing since things like that are very dangerous.
What you can do is have a map mapping the handler strings in your config file to the handler functions in your code:
var handlers = map[string]func(http.ResponseWriter, *http.Request) (int, error){
"Apple": AppleHandler,
"Banana": BananaHandler,
}
Then you can register those handlers by simply doing:
handler, ok := handlers[config.Handler]
if !ok {
log.Fatal(fmt.Errorf("Handler not found"))
}
r.HandleFunc(config.URL, handler).Methods(config.Method)
There's some limited way to access things during runtime with the reflect package. However it doesn't allow you to search for all suitable standalone functions in a package. It would be possible if they are all methods on a known struct type/value.
As an alternative your given example you could simply use a map[string]func(...) to store all handlers, initialize it at startup (during init()) and fetch the handlers from there. But that also more or less what the existing http muxes are doing.

Resources