Post request to Golang API to send JSON and stringy-fied JSON - json

While making a POST request to Golang API, if I send stringy-fied JSON data it returns success but when I send JSON data it returns error with status 403.
Please help me understanding this behavior and how can I send JSON data using a POST request method.
File: main.go
package main
import (
"devmgmtv2/auth"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"log"
"net/http"
)
func main() {
router := mux.NewRouter()
auth.AuthInit(router)
ssid.SsidInit(router)
headersOk := handlers.AllowedHeaders([]string{"X-Requested-With"})
originsOk := handlers.AllowedOrigins([]string{"*"})
methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"})
router.HandleFunc("/auth/login", Login).Methods("POST", "OPTIONS")
log.Fatal(http.ListenAndServe(":8000", handlers.CORS(headersOk, originsOk, methodsOk)(router)))
}
func Login(w http.ResponseWriter, r *http.Request) {
//Create User Struct
var user User
json.NewDecoder(r.Body).Decode(&user)
userPassword := getUserPassword(user.User)
// call get value for that user
// check for equality if true, return the structure
// if false return error
if user.Password == userPassword {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte("success"))
} else {
http.Error(w, "Forbidden", http.StatusForbidden)
}
}

When sending JSON to any http server you always have to use JSON.stringify().
Not doing so will result in sending [object Object]...
There are client libraries that do this kind of heavy lifting for you, but behind the scenes the JSON is always send as a string.
Node.JS handles it the same way... it receives the string representation and usually something like body parser is run on the incoming request to extract the JSON from the string. So it might happen here behind the scenes, but it still happens.

Related

Go Fiber not able to parse body in unit test

I am officially crying uncle to the benevolent samaritans of Stack Overflow.
I am trying to unit test my GORM (Postgres) + Fiber API using a mock DB. I have a Card model and a CreateCardReqBody model for the POST request body. To setup the test, I create a random CreateCardReqBody instance, marshal it into JSON, then pass it into an *httptest.Request. The handler uses Fiber's (*fiber.Ctx).BodyParser function to "unmarshal" the request body into an empty Card struct. However, when I run the test that is supposed to pass, Fiber throws an "Unprocessable Entity" error.
Below are the relevant parts of my code; the test file is a combination of this tutorial and Fiber's documentation on the (*App).Test method. (I realize the code could be cleaned up; I'm just trying to get a proof of life then focus on revising :)
I've done a few things to debug this: I've made a Postman POST request with the same values as the test and it works. Within the test itself, I marshal then unmarshal the CreateCardReqBody struct and that works. I've triple checked the spelling of the JSON fields match, that the struct fields are exported, etc. I've also run the VSCode debugger and the body field within Fiber.Ctx's also looks correct to me.
I'm starting to wonder if it's something with how Fiber parses the body from a test request vs. a real request. I would greatly appreciate any insight one could share on this!
Model Definition
type Card struct {
gorm.Model
// Implicit Gorm foreign key to fleet ID
FleetID uint `gorm:"index" json:"fleet_id" validate:"required,min=1"`
// Card provider's account number
ProviderAccountNumber string `json:"provider_account_number"`
// Card provider's external card identifier
CardIdentifier string `gorm:"index" json:"card_identifier" validate:"min=1"`
// Implicit Gorm foreign key to driver ID. Driver association is optional.
DriverID uint `json:"associated_driver_id" validate:"min=1"`
// Implicit Gorm foreign key to vehicle ID.
VehicleID uint `json:"associated_vehicle_id" validate:"required,min=1"`
// User-inputted start date, formatted "2020-01-26T22:38:25.000Z" in UTC
StartDate pq.NullTime
}
Test file
// Adapted from tutorial
type testCase struct {
name string
body CreateCardReqBody
setupAuth func(t *testing.T, request *http.Request)
buildStubs func(db *mockDB.MockDBInterface)
checkResponse func(response *http.Response, outputErr error)
}
type CreateCardReqBody struct {
FleetID int `json:"fleet_id"`
ProviderAccountNumber string `json:"provider_account_number"`
CardIdentifier string `json:"card_identifier"`
StartDate string `json:"start_date"`
AssociatedDriverID int `json:"associated_driver_id"`
AssociatedVehicleID int `json:"associated_vehicle_id"`
}
func TestCreateCard(t *testing.T) {
user := randomUser(t)
vehicle := randomVehicle()
driver := randomDriver(vehicle.FleetID)
okReqCard := randomCard(vehicle.FleetID)
finalOutputCard := okReqCard
finalOutputCard.ID = 1
testCases := []testCase{
{
name: "Ok",
body: CreateCardReqBody{
FleetID: int(okReqCard.FleetID),
ProviderAccountNumber: okReqCard.ProviderAccountNumber,
CardIdentifier: okReqCard.CardIdentifier,
StartDate: okReqCard.StartDate.Time.Format("2006-01-02T15:04:05.999Z"),
AssociatedDriverID: int(okReqCard.DriverID),
AssociatedVehicleID: int(okReqCard.VehicleID),
},
setupAuth: func(t *testing.T, request *http.Request) {
addAuthorization(t, request, user)
},
// Tell mock database what calls to expect and what values to return
buildStubs: func(db *mockDB.MockDBInterface) {
db.EXPECT().
UserExist(gomock.Eq(fmt.Sprint(vehicle.FleetID))).
Times(1).Return(user, true, user.ID)
db.EXPECT().
SearchTSP(gomock.Eq(fmt.Sprint(vehicle.FleetID))).
Times(1)
db.EXPECT().
SearchVehicle(gomock.Eq(fmt.Sprint(okReqCard.VehicleID))).
Times(1).
Return(vehicle, nil)
db.EXPECT().
SearchDriver(gomock.Eq(fmt.Sprint(driver.ID))).
Times(1).
Return(driver, nil)
db.EXPECT().
CardCreate(gomock.Eq(okReqCard)).
Times(1).
Return(finalOutputCard, nil)
},
checkResponse: func(res *http.Response, outputErr error) {
require.NoError(t, outputErr)
// Internal helper func, excluded for brevity
requireBodyMatchCard(t, finalOutputCard, res.Body)
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockDB := mockDB.NewMockDBInterface(ctrl)
test.buildStubs(mockDB)
jsonBytes, err := json.Marshal(test.body)
require.NoError(t, err)
jsonBody := bytes.NewReader(jsonBytes)
// Debug check: am I able to unmarshal it back? YES.
errUnmarsh := json.Unmarshal(jsonBytes, &CreateCardReqBody{})
require.NoError(t, errUnmarsh)
endpoint := "/v1/transactions/card"
request := httptest.NewRequest("POST", endpoint, jsonBody)
// setupAuth is helper function (not shown in this post) that adds authorization to httptest request
test.setupAuth(t, request)
app := Init("test", mockDB)
res, err := app.Test(request)
test.checkResponse(res, err)
})
}
}
Route handler being tested
func (server *Server) CreateCard(c *fiber.Ctx) error {
var card models.Card
var err error
// 1) Parse POST data
if err = c.BodyParser(&card); err != nil {
return c.Status(http.StatusUnprocessableEntity).SendString(err.Error())
}
...
}
Debugger Output
Json body when defined in test
Body inside Fiber context
facepalm
I forgot to request.Header.Set("Content-Type", "application/json")! Posting this in case it's helpful for anyone else :)

using http.PostForm to POST nested json data values?

I've seen the following example of how to use net/http PostForm to send a basic string map as a POST request:
How to send a POST request in Go?
But the data that I need to post is slightly more complicated as it has nested json / nested string maps. An example of the data I need to post:
{"MyAttributes" : {"AttributeOne" : "one", "AttributeTwo":"two"}}
Can net/url Values represent that kind of nested data and/or how do I pass this to net/http PostForm?
It's possible
package main
import (
"encoding/json"
"log"
"net/http"
"net/url"
)
type Attributes struct {
AttributeOne string
AttributeTwo string
}
func main() {
attributes := Attributes{"one", "two"}
data, err := json.Marshal(attributes)
if err != nil {
log.Fatal("bad", err)
}
values := url.Values{}
values.Set("MyAttributes", string(data))
resp, error := http.PostForm("localhost:2021", values)
// use resp and error later
}

How to mock http.Client that returns a JSON response

I'm trying to test a method that uses net/http to make requests. Specifically what I'm trying to achieve is to inject a mock http.Client that responds with a certain JSON body
type clientMock struct{}
func (c *clientMock) Do(req *http.Request) (*http.Response, error) {
json := struct {
AccessToken string `json:"access_token`
Scope string `json:"scope"`
}{
AccessToken: "123457678",
Scope: "read, write",
}
body := json.Marshal(json)
res := &http.Response {
StatusCode: http.StatusOK,
Body: // I haven't got a clue what to put here
}
return res
}
func TestRequest(t *testing.T) { //tests here }
I do know that the Body is of a type io.ReadCloser interface. Trouble is I can't for the life of me find a way to implement it in the mock body response.
Examples as found here so far only demonstrates returning a blank &http.Response{}
While it's probably more useful to mock the full request cycle with httptest.Server, you can use ioutil.NopCloser to create the closer around any reader:
Body: ioutil.NopCloser(bytes.NewReader(body))
and if you want an empty body, just provider a reader with no content.
Body: ioutil.NopCloser(bytes.NewReader(nil))
In your test file (my_test.go):
type MyJSON struct {
Id string
Age int
}
// Interface which is the same as httpClient because it implements "Do" method.
type ClientMock struct {}
func (c *ClientMock) Do(req *http.Request) (*http.Response, error) {
mockedRes := MyJSON {"1", 3}
// Marshal a JSON-encoded version of mockedRes
b, err := json.Marshal(mockedRes)
if err != nil {
log.Panic("Error reading a mockedRes from mocked client", err)
}
return &http.Response{Body: ioutil.NopCloser(bytes.NewBuffer(b))}, nil
}
// your test which will use the mocked response
func TestMyFunction(t *testing.T) {
mock := &ClientMock{}
actualResult := myFunction(mock)
assert.NotEmpty(t, actualResult, "myFunction should have at least 1 result")
}
In your implementation (main.go):
package main
import (
"net/http"
)
func main() {
myFunction(&http.Client{})
}
I know it's been a little while but I just wrote something to help with this recently.
Like JimB I recommend starting up a real HTTP server locally, since in Go this is easy to do with https://golang.org/pkg/net/http/httptest/.
However having done a lot of HTTP mocking I wanted something that does a little more, like a good mock library would: returning specific data, easy setting of expectations, validation that all requests were made, etc. I have generally used https://godoc.org/github.com/stretchr/testify/mock for mocking and wanted features like that.
So I wrote https://github.com/dankinder/httpmock, which basically combines the two. If you just want a mock that accepts JSON and spits out JSON, it may be an easier way to go.

Request Body is null in Go lang (Postman)

I am using Postman to post the json string on localhost. The json string that Iam passing in Postman is :
{
“name”: "foo"
}
However, when I retrieve the data in my test function, the req.Body i get something like this : &{%!s(*io.LimitedReader=&{0xc0820142a0 0}) <nil> %!s(*bufio.Reader=<nil>) %!s(bool=false) %!s(bool=true) {%!s(int32=0) %!s(uint32=0)} %!s(bool=true) %!s(bool=false) %!s(bool=false)}
I wish to get the name:foo in the request body.
My go lang code for the same is :
import (
"encoding/json"
"fmt"
"net/http"
)
type Input struct {
Name string `json:"name"`
}
func test(rw http.ResponseWriter, req *http.Request) {
var t Input
json.NewDecoder(req.Body).Decode(&t)
fmt.Fprintf(rw, "%s\n", req.Body)
}
func main() {
http.HandleFunc("/test", test)
http.ListenAndServe(":8080", nil)
}
Can anyone tell me why I am getting blank data in the req.Body attribute ? Thanks a lot.
Reuqes Body should be empty because you already read all from it. But that not the issue.
From your question, it seem your input is not valid JSON (you have “ which is different with ").
The Decode method will return error, you should check that.
if err := json.NewDecoder(req.Body).Decode(&t); err != nil {
fmt.Println(err)
}

JSON RPC Client Go

I have a python server serving response through JSON-RPC. Here is a sample response from the server.
'{"jsonrpc": "2.0", "result": "Peer 1: local 10.10.0.2 remote 10.10.0.1 state CONNECT\\nPeer 2: local 10.10.0.18 remote 10.10.0.17 state ESTABLISHED\\nPeer 3: local 10.10.0.10 remote 10.10.0.9 state ESTABLISHED", "id": "839732f9-cf36-46ff-8b9b-6120250d9ce5"}'
Here is the request I need to send to the server:
'{"method":"echo","jsonrpc":"2.0","params":["test_params"],"id":"839732f9-cf36-46ff-8b9b-6120250d9ce5"}'
Here is my client with go language:
package main
import (
"fmt"
"log"
"net"
"net/rpc/jsonrpc"
)
type Args struct {
jsonrpc, id string
}
func main() {
conn, err := net.Dial("tcp", "11.21.22.221:8080")
if err != nil {
panic(err)
}
defer conn.Close()
args := Args{"2.0", "d87198f0-af92-49f8-9a7d-ab8bed5c4d17"}
var reply string
c := jsonrpc.NewClient(conn)
err = c.Call("echo", args, &reply)
if err != nil {
log.Fatal("error:", err)
}
fmt.Printf("Response: %d", reply)
}
But, when I run this client, it is not sending anything in the params. Instead it is sending empty params like this:
'{"method":"echo","params":[{}],"id":0}\n
Can somebody help me telling what mistake I am making? I am a newbie to go language.
Thanks.
I don't think what you are doing is possible using the client as provided by go because the private clientRequest struct is currently defined as:
type clientRequest struct {
Method string `json:"method"`
Params [1]interface{} `json:"params"`
Id uint64 `json:"id"`
}
What you pass into Call as args is stuck into Params and note how there is no "Version `json:"jsonrpc"`" inside of that struct.
AFAICT (which may be wrong, this is my first time reading through this code) you would need to implement your own ClientCodec. You could probably get away with copying most (all) of the parts out of the stdlib and add the field to the clientRequest above. ;-)