go-json-rest: JSON payload is empty - json

I'm trying to use go-json-rest to create RESTful APIs
I have this model struct:
type Todo struct {
id int
name string
}
And I am trying to do a POST request to create an object of Todo type:
func CreateTodo(w rest.ResponseWriter, r *rest.Request) {
body, _ := ioutil.ReadAll(r.Body)
log.Println("body content is: ", string(body)) // here I can see {"name": "test1"}
var todo Todo = Todo{}
err := r.DecodeJsonPayload(&todo) // here the error shows JSON payload is empty
defer r.Body.Close()
if err != nil {
log.Println("error in parsing json")
rest.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if todo.name == "" {
rest.Error(w, "todo name is required", 400)
return
}
lock.Lock()
var nextId int = len(todos)
// todos[nextId] = todo
todo.id = nextId // set its id
todos = append(todos, todo)
log.Println("num of todos: ", len(todos))
lock.Unlock()
w.WriteJson(&todo)
}
However, in the console of the POSTMAN client, the error shows:
{
"Error": "JSON payload is empty"
}
I was wondering where might go wrong. Thanks
Edit:
This should not be considered a duplicate question, because I am not even trying to use the json package to do marshalling/unmarshalling of JSON object. Instead I am using the rest.Request object (built in go-json-rest) to decode the json parameters as posted from client.
After much digging and search on this problem I found this answer below resolved my issue:
body, _ := ioutil.ReadAll(r.Body) will consume everything from the
request body. So after removing this line, the json parsing works!

I was just being silly doing body, _ := ioutil.ReadAll(r.Body) before decoding the JSON parameters without really understanding what ioutil.ReadAll() does to the request body.
As I quoted above in the edited post, ioutil.ReadAll() consumes everything in the request body, leaving nothing for the json decoder to parse. After removing the line body, _ := ioutil.ReadAll(r.Body), the json parsing works as expected.

Related

Golang http client always performs get requests and does not post

I'm currently experiencing a problem that really frustrates me and where i absolutely can't see anny issues with my code.
What i'm trying to achieve is to send a http POST message to a mockup of an iDrac i wrote (both softwares written in golang) to control the mockup's powerstate, but no matter what i configure for the request, the mockup always receives get requests with an empty body.
The function i create and send the request with:
func (iDrac *IDrac) SetPowerstate(powerstate string) error {
//Create reset type json string
var jsonStr = `{"ResetType":"` + powerstate + `"}`
//Create the request with auth header
req, reqErr := iDrac.buildHttpRequestWithAuthorizationHeader(http.MethodPost, "https://"+iDrac.hostAddress+apiBaseURL+apiChassisURL+apiChassisResetAction, jsonStr)
if reqErr != nil {
return fmt.Errorf("COULD_NOT_CREATE_REQUEST: " + reqErr.Error())
}
req.Header.Set("Content-Type", "application/json; charset=UTF-8")
//Make the request
resp, doErr := http.DefaultClient.Do(req)
if doErr != nil {
return fmt.Errorf("COULD_NOT_SEND_POST_REQUEST_TO_IDRAC_API" + doErr.Error())
}
//Check if the request was successful
if resp.StatusCode != 200 {
return fmt.Errorf("COULD_NOT_CHANGE_SERVER_POWER_STATUS_OVER_IDRAC HTTP:" + resp.Status)
}
return nil
}
The helper function i use to build the request with:
func (iDrac *IDrac) buildHttpRequestWithAuthorizationHeader(method string, url string, content string) (*http.Request, error) {
req, err := http.NewRequest(method, url, bytes.NewBuffer([]byte(content)))
if err != nil {
return nil, err
}
req.Header.Add("Authorization", "Basic "+iDrac.hashedCredentials)
return req, nil
}
And finally the function where the mockup proccesses the request:
func handlePerformReset(w http.ResponseWriter, r *http.Request) {
garbagelog.Log("Handling reset perform request from " + r.RemoteAddr)
if r.Method != http.MethodPost {
garbagelog.Log("Invalid http method! Got " + r.Method + " expected POST")
w.WriteHeader(405)
return
}
if !checkAuthorization(r.BasicAuth()) {
w.WriteHeader(401)
return
}
var resetType nidrac.ResetType
err := json.NewDecoder(r.Body).Decode(&resetType)
if err != nil {
garbagelog.Log("Could not decode reset type: " + err.Error())
w.WriteHeader(422)
return
}
iDracMock.PerformResetAction(resetType.ResetType)
garbagelog.Log(">>>>SEVERSTATE: " + iDracMock.PowerState().PowerState + "<<<<")
w.WriteHeader(200)
}
The type the iDrac mock tries to convert the body to:
type ResetType struct {
ResetType string
}
It works flawlessly when i try to reach the endpoint with postman:
iDrac mockup log confirming the successful request
Postnam request configuration:
Postman request configuration
But it somehow does not work when i try making the request with go code:
iDrac mockup log saying that the http method is invalid (because it's get instead of post)
I spent two hours trying to find a solution but i somehow did not find a post with someone having the same problem that i have.
Edit: Corrected old code. The problem remains even with the silly mistake fixed:
//Create the request with auth header
req, reqErr := iDrac.buildHttpRequestWithAuthorizationHeader(http.MethodPost, "https://"+iDrac.hostAddress+apiBaseURL+apiChassisURL+apiChassisResetAction, jsonStr)
if reqErr != nil {
return fmt.Errorf("COULD_NOT_CREATE_REQUEST: " + reqErr.Error())
}
How i build the routes in the iDrac mockup:
http.Handle("/", http.HandlerFunc(handleDefault))
http.Handle("/reset", http.HandlerFunc(handleReset))
http.Handle("/redfish/v1/Systems/System.Embedded.1", http.HandlerFunc(handlePowerStateGet))
http.Handle("/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset", http.HandlerFunc(handlePerformReset))
garbagelog.Log("Starting webserver")
err := http.ListenAndServeTLS(":443", currentConfig.CertFile, currentConfig.PrivKeyFile, nil)
if err != nil {
garbagelog.Log("Could not serve TLS: " + err.Error())
}
In both requests, the one created in my iDrac comms module and the one received by the iDrac mockup, did confirm that the requested path is:
r.URL.Path = /redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset
I found the problem:
The constants i built the urls with were defined like this:
const (
apiBaseURL = "/redfish/v1/"
apiChassisURL = "/Systems/System.Embedded.1"
apiChassisResetAction = "/Actions/ComputerSystem.Reset"
)
Leading to a url that looks like this:
https://host/redfish/v1//Systems/System.Embedded.1/Actions/ComputerSystem.Reset
(Notice the double // between v1 and Systems)
So i've fixed it:
const (
apiBaseURL = "/redfish/v1"
apiChassisURL = "/Systems/System.Embedded.1"
apiChassisResetAction = "/Actions/ComputerSystem.Reset"
)
And everything works correctly:
Test results showing that every test was successful
I thank everyone for their input for helping me not lose my mind completely.
based on your screenshot, I can see you are using a "POST" request in your postman, but in your code is "GET".
I think nothing wrong with the code but the method only :)

golang unmarshall unknown json data

I have a use case where I need to unmarshal response from HTTP requests. I don't know the response format ahead of time but would just like to return the result upstream to web client (similar functionality to a proxy)
Normally I would just unmarshal like the following:
resp, _ = http.Post(url, "application/json", bytes.NewBuffer(jsonPayload))
body, _ := ioutil.ReadAll(resp.Body)
responseJson := make(map[string]interface{})
json.Unmarshal(body, &responseJson)
However, if the result is instead an array of JSON [{},{}...]
then I would need to do the following
var responseList []map[string]interface{}
json.Unmarshal([]byte(body), &responseList)
And if the result is a single string value like "ok" it would also require different unmarshaling methods
But If I don't know the response type ahead of time, how would I know how to unmarshal?
You can just unmarshall it as interface like :
var responseJson interface{}
json.Unmarshal(body, &responseJson)
To read the type of response:
switch resp := responseBody.(type) {
case string:
fmt.Println(resp)
case float64:
fmt.Println(int(resp))
default:
fmt.Println(resp)
}

Parsing JSON with GoLang in AWS Lamda

As part of an application we are building, one of the process steps is an AWS Lamda that captures a post request does some work with it, and then moves one. It has an API Gateway Request as a trigger and the body of this request would be a JSON String. I am having trouble parsing the JSON String to GoLang Object. Here is what I have:
The function that catches request:
func HandleRequest(ctx context.Context, event events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
log.Print(fmt.Sprintf("body:[%s] ", event.Body))
parseResponseStringToTypedObject(event.Body)
return events.APIGatewayProxyResponse{
StatusCode: http.StatusOK,
Body: "OK",
}, nil
}
Then the parseResponseStringToTypedObject function :
func parseResponseStringToTypedObject(responseString string) {
b := []byte(responseString)
var resp SimpleType
err := json.Unmarshal(b, &resp)
if err == nil {
log.Print(fmt.Sprintf("Account Name: [%s]", resp.accountName))
} else {
log.Print(fmt.Sprintf("Could not unmarshall JSON string: [%s]", err.Error()))
}
}
Here is the SimpleType struct:
type SimpleType struct {
accountName string `json:accountName`
amount int `json:amount`
}
I then, as a test, posted this JSON Body via Postman:
I opened up the CloudWatch Logs (where my lamda logs to) and see that the body is present in the event.Body property, and then logging out a field in the unmarshalled object (resp.accountName) I note that the field is blank. Why is this? Here is log output for the request:
Your SimpleType struct needs 2 things here...
1) The properties need to be "public" or "exported". Meaning they need to start with an upper cased character.
AND
2) The properties need proper tags for the serialization and de serialization of JSON. e.g. each JSON tag surrounded by "
type SimpleType struct {
AccountName string `json:"accountName"`
Amount int `json:"amount"`
}
Hope this helps!

Golang not producing error when decoding "{}" body into struct

In a rest api, when the body is set to "{}", the json Decoder will not generate an error. This makes it necessary to check if the target struct is still nil.
I need to check if the library is supposed to work like this, or if this is an issue with it.
// Client Side this request
req, err := http.NewRequest("POST", "url", strings.NewReader("{}") )
// Curl equivalent:
curl -X POST -d '{}' http://api:8080/r/primitives/multiply
// Server side
type Operands struct {
Values []float64 `json:"Values"`
}
func handler(req *http.Request, res *http.Response) (string, error) {
operands := Operands{}
err := json.NewDecoder(req.Body).Decode(&operands)
if err != nil {
res.StatusCode = 400
res.Status = http.StatusText(res.StatusCode)
http.StatusText(res.StatusCode)
return "", err
}
operands.Values[0] // It will fail here.
}
Edit 1: The decoder works fine with and empty body "" with the error being generated, and works fine with a correct body like this one: {"Values" : [ 5.0 , 2.0 ]}
Edit 2: The issue here is that with a "{}" body, it will not return an error when decoding, instead it will keep the target struct as nil.
{} is just an empty Json object, and it will decode fine to your Operandsstruct, as the struct is not required to have anything in the Operands array.
You need to validate that yourself, e.g.
err := json.NewDecoder(req.Body).Decode(&operands)
if err != nil || len(operands.Values) == 0{

Passing JSON parameter to function in GOLANG

I want to pass a JSON object to a function in GOLANG ,so how would I define my parameters,would it be fine if I can define my params as string .below is a sample
func getDetailedNameSpace(authentication string,id string) string{
var jsonStr = []byte(string);
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
req, _ := http.NewRequest("PUT", "https://"+authentication.endPoint+"/object/namespaces/"+id, bytes.NewBuffer(jsonStr))
req.Header.Add("X-Sds-Auth-Token",authentication.authtoken)
req.Header.Add("Accept","application/json")
res,err:=client.Do(req)
body, err := ioutil.ReadAll(res.Body)
if err!=nil{
fmt.Printf("%s",err)
}
return string(body);
}
Also i have to validate the params ,as in python we can have like below
def getDetailedNameSpace(authentication,id=None):
assert authentication!=None,"Authentication Required"
I'm assuming you're trying to put the JSON as the body of your PUT request. In this case you'll just want to use assignment. The request object has a field of type io.ReadCloser for it.
req.Header.Add("Accept","application/json")
req.Body = bytes.NewBufferString(MyJsonAsAString)
res,err:=client.Do(req)
There are some other methods like http.Post which take the body as an argument but in this case, the Do method takes a Request object as an argument and that has a property for the Body.