Golang Chi router not rendering response payload JSON - json

Go here using Chi renderer for a basic REST service. I have the following structs and functions:
type Order struct {
OrderId uuid.UUID `json:"orderId",gorm:"type:uuid;primary_key;not null;default gen_random_uuid()"`
Quantity int `json:"quantity",gorm:"not null"`
Status string `json:"status",gorm:"not null"`
}
func (o *Order) Bind(r *http.Request) error {
return nil
}
func (o *Order) Render(w http.ResponseWriter, r *http.Request) error {
return nil
}
func NewOrdersList(orders []Order) []render.Renderer {
list := []render.Renderer{}
for _, order := range orders {
list = append(list, &order)
}
return list
}
func GetOrderByIdHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
orderId := chi.URLParam(r, "orderId")
order, err := fetchOrder(orderId)
if err != nil {
render.Render(w, r, NewInternalServerError(err))
return
}
log.Info("order status is " + order.Status)
render.Bind(r, &order)
return
}
}
When I run this and hit the endpoint that invokes the GetOrderByIdHandler() function, I get back a 200 OK/Success. However there is no JSON in the response body, whereas I would have expected a marshalled JSON payload representing an "order", such as:
{
"orderId": "12345",
"quantity": 1,
"status": "SENT"
}
However my curl shows nothing in the response body:
$ curl -i -H "Content-Type: application/json" -H "Accept: application/json" -X GET http://localhost:9400/myapp/v1/orders/12345
HTTP/1.1 200 OK
Vary: Origin
Date: Wed, 24 Jun 2020 07:09:30 GMT
Content-Length: 0
Any idea where I'm going awry? I do see the log statement print out the order status right before calling bind, so I know its not a null/empty order instance.

render.Bind is input-only, i.e. for decoding the request payload. Instead use render.JSON to send a json response.
func GetOrderByIdHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
orderId := chi.URLParam(r, "orderId")
order, err := fetchOrder(orderId)
if err != nil {
render.Render(w, r, NewInternalServerError(err))
return
}
log.Info("order status is " + order.Status)
render.JSON(w, r, order)
}
}
Or, alternatively, you could also use the standard approach: import the encoding/json package and then use it like so:
func GetOrderByIdHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
orderId := chi.URLParam(r, "orderId")
order, err := fetchOrder(orderId)
if err != nil {
render.Render(w, r, NewInternalServerError(err))
return
}
log.Info("order status is " + order.Status)
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(order); err != nil {
render.Render(w, r, NewInternalServerError(err))
}
}
}
Also note that the proper format for multiple struct tags is "space delimited" not "comma separated". For example: json:"quantity" gorm:"not null" is correct, while json:"quantity",gorm:"not null" is not.

Related

How to send the correct JSON response message format

I have a Go program which I want to print the JSON response message:
func MyPluginFunction(w http.ResponseWriter, r *http.Request) {
data := `{"status":"false","error":"bad request"}`
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest )
json.NewEncoder(w).Encode(data)
}
However, when I used this function, I got a weird format in the JSON format. It looks like this:
"{\"status\":\"false\",\"error\":\"bad request\"}"
Is there any way to make the response message becomes a normal JSON, like:
{
"status": "false",
"error": "bad request"
}
Your data already contains JSON encoded data, so you should just write it as-is, without re-encoding it:
func MyPluginFunction(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest )
data := `{"status":"false","error":"bad request"}`
if _, err := io.WriteString(w, data); err != nil {
log.Printf("Error writing data: %v", err)
}
}
If you pass data to Encoder.Encode(), it is treated as a "regular" string and will be encoded as such, resulting in a JSON string where double quotes are escaped as per JSON rules.
You only have to JSON encode if you have a non-JSON Go value, such as in this example:
func MyPluginFunction(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
data := map[string]any{
"status": "false",
"error": "bad request",
}
if err := json.NewEncoder(w).Encode(data); err != nil {
log.Printf("Error writing data: %v", err)
}
}

How to minimize duplicate code in Go Mux when always trying to return same response structure?

I have tons of code similar to the following code snippet that I just try to fill my response struct, json marshal the output, set status code and return the result:
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
response := responses.UserResponse{
Status: http.StatusBadRequest,
Message: "error",
Data: map[string]interface{}{"error": err.Error()},
}
rw.WriteHeader(http.StatusBadRequest)
errRes, _ := json.Marshal(response)
rw.Write(errRes)
return
}
I tried to create a function that receives r variable (request.http) to receive the body and also status code of the response. But noticed that I have to again check error code outside of the function and then do the same response creation flow again.
How someone expert in Go tries to minimize code duplications like these? Is this OK to have code duplication like these in first place?
Minimize code duplication by moving the decode call and error handling to a reusable function:
// Decode returns true if the request body is successfully decoded
// to the value pointed to by pv. Otherwise, decode writes an error
// response and returns false.
func decode(rw http.ResponseWriter, r *http.Request, pv interface{}) bool {
err := json.NewDecoder(r.Body).Decode(pv)
if err == nil {
return true
}
rw.WriteHeader(http.StatusBadRequest)
json.NewEncoder(rw).Encode(map[string]any{
"status": http.StatusBadRequest,
"message": "error",
"data": map[string]any{"error": err.Error()},
})
return false
}
Use the function like this:
func userHandler(rw http.ResponseWriter, r *http.Request) {
var u UserRequest
if !decode(rw, r, &u) {
return
}
}
It is preferable to abstract details to provide a high-level picture of what your handler does.
func (h *rideHandler) handleCancelRideByPassenger(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user := getUser(ctx)
req := &cancelRequest{}
if err := decode(r, req); err != nil {
h.logger.Error("cancel ride: problem while decoding body request", zap.String("ip", r.RemoteAddr), zap.Error(err))
h.respond.BadRequest(w, NewRESTError(reasonDecoding, "problem while decoding input parameters"))
return
}
req.PublicID = chi.URLParam(r, "id")
err := h.rideService.CancelRide(ctx, req, user)
if err != nil {
var validationErr *ValidationError
switch {
case errors.As(err, &validationErr):
h.respond.BadRequest(w, NewRESTValidationError(reasonValidation, "problem while validating request", validationErr))
return
default:
h.respond.InternalServerError(w, NewRESTError(reasonInternalError, "unknown problem occurred"))
return
}
}
h.respond.Ok(w, NewRESTResponse(&cancelRideResponse{Success: true}))
}
Handler utilizes some handy sugar functions to remove duplication and provide high-level overview of what handler does instead underlying details.
func decode(request *http.Request, val interface{}) error {
dec := json.NewDecoder(request.Body)
dec.DisallowUnknownFields()
return dec.Decode(val)
}
type Responder struct {
Encoder Encoder
Before BeforeFunc
After AfterFunc
OnError OnErrorFunc
}
func (r *Responder) writeResponse(w http.ResponseWriter, v interface{}, status int) {
if r.Before != nil {
status, v = r.Before(w, v, status)
}
encoder := JSON
if r.Encoder != nil {
encoder = r.Encoder
}
w.Header().Set("Content-Type", encoder.ContentType())
w.WriteHeader(status)
if err := encoder.Encode(w, v); err != nil {
if r.OnError != nil {
r.OnError(err)
}
}
if r.After != nil {
r.After(v, status)
}
}
func (r *Responder) Ok(w http.ResponseWriter, v interface{}) {
r.writeResponse(w, v, http.StatusOK)
}
Probably you should write your own respond package or check what is available in open source. Then you can use this respond package with the same response structure everywhere.

gzip compression to http responseWriter

I'm new to Go. But am playing with a REST Api. I cant get the same behavior out of json.Marshal as json.Encoder in two functions i have
I wanted to use this function to gzip my responses:
func gzipFast(a *[]byte) []byte {
var b bytes.Buffer
gz := gzip.NewWriter(&b)
defer gz.Close()
if _, err := gz.Write(*a); err != nil {
panic(err)
}
return b.Bytes()
}
But this function returns this:
curl http://localhost:8081/compressedget --compressed --verbose
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8081 (#0)
> GET /compressedget HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/7.47.0
> Accept: */*
> Accept-Encoding: deflate, gzip
>
< HTTP/1.1 200 OK
< Content-Encoding: gzip
< Content-Type: application/json
< Date: Wed, 24 Aug 2016 00:59:38 GMT
< Content-Length: 30
<
* Connection #0 to host localhost left intact
Here is the go function:
func CompressedGet(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
box := Box{Width: 10, Height: 20, Color: "gree", Open: false}
box.ars = make([]int, 100)
for i := range box.ars {
box.ars[i] = i
}
//fmt.Println(r.Header.Get("Content-Encoding"))
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Encoding", "gzip")
b, _ := json.Marshal(box)
//fmt.Println(len(b))
//fmt.Println(len(gzipFast(&b)))
fmt.Fprint(w, gzipFast(&b))
//fmt.Println(len(gzipSlow(b)))
//gz := gzip.NewWriter(w)
//defer gz.Close()
//json.NewEncoder(gz).Encode(box)
r.Body.Close()
}
But when i uncomment:
//gz := gzip.NewWriter(w)
//defer gz.Close()
//json.NewEncoder(gz).Encode(box)
it works fine.
I would avoid gzip-ing []byte manually. You can easily use already existing writers from standard library. Additionally, take a look on compress/flate which I think should be use instead of gzip.
package main
import (
"net/http"
"encoding/json"
"compress/gzip"
"log"
)
type Box struct {
Width int `json:"width"`
}
func writeJsonResponseCompressed(w http.ResponseWriter, r *http.Request) {
box := &Box{Width: 10}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Encoding", "gzip")
body, err := json.Marshal(box)
if err != nil {
// Your error handling
return
}
writer, err := gzip.NewWriterLevel(w, gzip.BestCompression)
if err != nil {
// Your error handling
return
}
defer writer.Close()
writer.Write(body)
}
func main() {
http.HandleFunc("/compressedget", writeJsonResponseCompressed)
log.Fatal(http.ListenAndServe(":8081", nil))
}
You need to flush or close your gzip writer before accessing the underlying bytes, e.g.
func gzipFast(a *[]byte) []byte {
var b bytes.Buffer
gz := gzip.NewWriter(&b)
if _, err := gz.Write(*a); err != nil {
gz.Close()
panic(err)
}
gz.Close()
return b.Bytes()
}
Otherwise what's been buffer in the gzip writer, but not yet written out to the final stream isn't getting collected up.
I think the problem is the use of fmt.Fprint(w, gzipFast(&b)).
If you look to the definition of gzipFast it returns a []byte. You are putting this slice into the print function, which is "printing" everything into w.
If you look at the definition of the io.Writer:
type Writer interface {
Write(p []byte) (n int, err error) }
You see that the writer can handle []byte as input.
Instead of fmt.Fprint(w, gzipFast(&b)) you should use w.Write(gzipFast(&b)). Then you don't need to uncomment:
//gz := gzip.NewWriter(w)
//defer gz.Close()
//json.NewEncoder(gz).Encode(box)
Everything as a small example, which shows what is happening in your code:
https://play.golang.org/p/6rzqLWTGiI

How to serve up a JSON response using Go?

Question: Currently I'm printing out my response in the func Index
like this fmt.Fprintf(w, string(response)) however, how can I send JSON properly in the request so that it maybe consumed by a view?
package main
import (
"fmt"
"github.com/julienschmidt/httprouter"
"net/http"
"log"
"encoding/json"
)
type Payload struct {
Stuff Data
}
type Data struct {
Fruit Fruits
Veggies Vegetables
}
type Fruits map[string]int
type Vegetables map[string]int
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
response, err := getJsonResponse();
if err != nil {
panic(err)
}
fmt.Fprintf(w, string(response))
}
func main() {
router := httprouter.New()
router.GET("/", Index)
log.Fatal(http.ListenAndServe(":8080", router))
}
func getJsonResponse()([]byte, error) {
fruits := make(map[string]int)
fruits["Apples"] = 25
fruits["Oranges"] = 10
vegetables := make(map[string]int)
vegetables["Carrats"] = 10
vegetables["Beets"] = 0
d := Data{fruits, vegetables}
p := Payload{d}
return json.MarshalIndent(p, "", " ")
}
You can set your content-type header so clients know to expect json
w.Header().Set("Content-Type", "application/json")
Another way to marshal a struct to json is to build an encoder using the http.ResponseWriter
// get a payload p := Payload{d}
json.NewEncoder(w).Encode(p)
Other users were commenting that the Content-Type is plain/text when encoding.
You have to set the content type with w.Header().Set() first, then write the HTTP response code with w.WriteHeader().
If you call w.WriteHeader() first, then call w.Header().Set() after you will get plain/text.
An example handler might look like this:
func SomeHandler(w http.ResponseWriter, r *http.Request) {
data := SomeStruct{}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(data)
}
You can do something like this in you getJsonResponse function -
jData, err := json.Marshal(Data)
if err != nil {
// handle error
}
w.Header().Set("Content-Type", "application/json")
w.Write(jData)
In gobuffalo.io framework I got it to work like this:
// say we are in some resource Show action
// some code is omitted
user := &models.User{}
if c.Request().Header.Get("Content-type") == "application/json" {
return c.Render(200, r.JSON(user))
} else {
// Make user available inside the html template
c.Set("user", user)
return c.Render(200, r.HTML("users/show.html"))
}
and then when I want to get JSON response for that resource I have to set "Content-type" to "application/json" and it works.
I think Rails has more convenient way to handle multiple response types, I didn't see the same in gobuffalo so far.
You may use this package renderer, I have written to solve this kind of problem, it's a wrapper to serve JSON, JSONP, XML, HTML etc.
This is a complement answer with a proper example:
func (ch captureHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, fmt.Sprintf("error reading request body, %v", err), http.StatusInternalServerError)
return
}
...do your stuff here...
case http.MethodGet:
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode( ...put your object here...)
if err != nil {
http.Error(w, fmt.Sprintf("error building the response, %v", err), http.StatusInternalServerError)
return
}
default:
http.Error(w, fmt.Sprintf("method %s is not allowed", r.Method), http.StatusMethodNotAllowed)
}
}

Getting a Bad response - 422 from the server while sending a POST request in GO

I am trying to send a POST request using this function -
{
func (Client *Client) doModify(method string, url string, createObj interface{}, respObject interface{}) error {
bodyContent, err := json.Marshal(createObj)
if err != nil {
return err
}
client := Client.newHttpClient()
req, err := http.NewRequest(method, url, bytes.NewBuffer(bodyContent))
if err != nil {
return err
}
Client.setupRequest(req)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Content-Length", string(len(bodyContent)))
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
return errors.New(fmt.Sprintf("Bad response from [%s], go [%d]", url, resp.StatusCode))
}
byteContent, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return json.Unmarshal(byteContent, respObject)
}
}
I am calling my function like this -
{
func TestContainerCreate(t *testing.T) {
client := newClient(t)
container, err := client.Container.Create(&Container{
Name: "name",
ImageUuid: "xyz",
})
if err != nil {
t.Fatal(err)
}
defer client.Container.Delete(container)
}
}
The Create function calls internally calls the doCreate function which calls the doModify function pasted on the top .
{
func (self *ContainerClient) Create(container *Container) (*Container, error) {
resp := &Container{}
err := self.Client.doCreate(container_TYPE, container, resp)
return resp, err
}
}
{
func (Client *Client) doCreate(schemaType string, createObj interface{}, respObject interface{}) error {
if createObj == nil {
createObj = map[string]string{}
}
schema, ok := Client.Types[schemaType]
if !ok {
return errors.New("Unknown schema type [" + schemaType + "]")
}
return Client.doModify("POST", collectionUrl, createObj, respObject)
}
}
This gives me a 422 bad response.On doing further research, When doing a CURL, with "name" and "imageUuid" first letter as small case, gives a 201 created status but when passing "Name" and "ImageUuid" first letter as capital gives 422 bad response. Could there be issue with the json struct defined for container, or case of these entities being defined or something else?
{
curl -X POST -v -s http://localhost:8080/v1/containers -H 'Content-Type: application/json' -d '{"name" : "demo", "imageUuid" : "docker:nginx"}' | python -m 'json.tool'
}
Container struct definition looks like this -
{
type Container struct {
Resource
ImageId string `json:"ImageId,omitempty"`
ImageUuid string `json:"ImageUuid,omitempty"`
MemoryMb int `json:"MemoryMb,omitempty"`
Name string `json:"Name,omitempty"`
}
type ContainerCollection struct {
Collection
Data []Container `json:"data,omitempty"`
}
}
string(len(bodyContent)) isn't doing what you think it is. You're converting a single int to a utf-8 string. You want to use the strconv package to get the numerical representation.
Also note that you can't omitempty an int, since 0 is a valid value.