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?
Related
in my case, I want to use anothor param instead of callback
my url: http://example.com?id=1&cb=callback1
but I found this in the source code:
// JSONP serializes the given struct as JSON into the response body.
// It adds padding to response body to request data from a server residing in a different domain than the client.
// It also sets the Content-Type as "application/javascript".
func (c *Context) JSONP(code int, obj interface{}) {
callback := c.DefaultQuery("callback", "")
if callback == "" {
c.Render(code, render.JSON{Data: obj})
return
}
c.Render(code, render.JsonpJSON{Callback: callback, Data: obj})
}
how can I use the param cb instead of callback
You can use middleware for gin. Modifying the query before it is parsed.
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/url"
)
func main() {
r := gin.Default()
r.Use(func(context *gin.Context) {
req := context.Request
urlCopy, _ := req.URL.Parse(req.RequestURI)
if cb := urlCopy.Query().Get("cb"); cb != "" {
req.URL.RawQuery += fmt.Sprintf("&callback=%s", url.QueryEscape(cb))
}
})
r.GET("/ping", func(c *gin.Context) {
c.JSONP(400, 1)
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
How do I get the body that was sent?
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
fmt.Println("Hello, world!")
r := gin.Default()
r.POST("/", func(c *gin.Context) {
body := c.Request.Body
c.JSON(200,body);
})
r.Run(":8080");
}
I make a request via postman
{
"email": "test#gmail.com",
"password": "test"
}
and in response I get empty json {}
what to do?
You can bind the incoming request json as follows:
package main
import (
"github.com/gin-gonic/gin"
)
type LoginReq struct {
Email string
Password string
}
func main() {
r := gin.Default()
r.POST("/", func(c *gin.Context) {
var req LoginReq
c.BindJSON(&req)
c.JSON(200, req)
})
r.Run(":8080")
}
Remember this method gives 400 if there is a binding error. If you want to handle error yourself, try ShouldBindJSON which returns an error if any or nil.
I am trying to mock an HTTP client that's being used within an API function call in my Go code.
import (
"internal.repo/[...]/http"
"encoding/json"
"strings"
"github.com/stretchr/testify/require"
)
func CreateResource(t *testing.T, url string, bodyReq interface{}, username string, password string, resource string) []byte {
bodyReqJSON, err := json.Marshal(bodyReq)
if err != nil {
panic(err)
}
headers := make(map[string]string)
headers["Content-Type"] = "application/json"
logger.Logf(t, "*************************** CREATE a temporary test %s ***************************", resource)
// this func below should be mocked
statusCode, body := http.POST(t, url, bodyReqJSON, headers, username, password)
require.Equal(t, statusCode, 201, "******ERROR!! A problem occurred while creating %s. Body: %s******", resource, strings.TrimSpace(string(body)))
return body
}
I'd like to mock my http.POST function that it's part of an internal HTTP package so that I do not need to actually make the online call, and isolate the test offline.
Is there an alternative way to dependency-inject a mock structure that implements an hypothetical HTTP interface?
How would you do something like this?
Here's the solution, thanks to #Peter.
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCreateResource(t *testing.T) {
t.Run("successful", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(201)
}))
defer server.Close()
o := CreateResource(t, server.URL, nil, "admin", "password", "resource")
assert.Equal(t, []byte{}, o)
})
}
I have this code :
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.New()
r.GET("/user/:id", func(c *gin.Context) {
// How can I get the litteral string "/user/:id" here ?
c.JSON(http.StatusOK, gin.H{"message": "received request"})
})
}
Is there any way that I can retrieve inside the handler the litteral string /user/:id? If I use c.Request.Path it will give me the full output of the path like /user/10.
According to the documentation you can use FullPath().
router.GET("/user/:id", func(c *gin.Context) {
c.FullPath() == "/user/:id" // true
})
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)
}