298

I tried working with Apiary and made a universal template to send JSON to mock server and have this code:

package main

import (
    "encoding/json"
    "fmt"
    "github.com/jmcvetta/napping"
    "log"
    "net/http"
)

func main() {
    url := "http://restapi3.apiary.io/notes"
    fmt.Println("URL:>", url)

    s := napping.Session{}
    h := &http.Header{}
    h.Set("X-Custom-Header", "myvalue")
    s.Header = h

    var jsonStr = []byte(`
{
    "title": "Buy cheese and bread for breakfast."
}`)

    var data map[string]json.RawMessage
    err := json.Unmarshal(jsonStr, &data)
    if err != nil {
        fmt.Println(err)
    }

    resp, err := s.Post(url, &data, nil, nil)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("response Status:", resp.Status())
    fmt.Println("response Headers:", resp.HttpResponse().Header)
    fmt.Println("response Body:", resp.RawText())

}

This code doesn't send JSON properly, but I don't know why. The JSON string can be different in every call. I can't use Struct for this.

Marian
  • 13,247
  • 5
  • 30
  • 40
Ladislav Prskavec
  • 3,421
  • 2
  • 15
  • 15
  • I'm not familiar with some of the libraries you use, but as I understand it, you are trying to send a map of Jsons. Why don't you just send the string with the json? – Topo Jun 27 '14 at 15:28
  • 1
    why are you unmarshaling the json if you want to send json? – JimB Jun 27 '14 at 15:30
  • A little tip, you can create your message as a struct or map[string]interface{} to add all the values you want and then use json.Marshall to convert the map or struct to a json. – Topo Jun 27 '14 at 15:41
  • @topo, i dug into napping's source code, if payload is set, they call `json.Marshall` on it, I'm not sure why it wasn't working for him. – OneOfOne Jun 27 '14 at 19:44

7 Answers7

585

I'm not familiar with napping, but using Golang's net/http package works fine (playground):

func main() {
    url := "http://restapi3.apiary.io/notes"
    fmt.Println("URL:>", url)

    var jsonStr = []byte(`{"title":"Buy cheese and bread for breakfast."}`)
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
    req.Header.Set("X-Custom-Header", "myvalue")
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    fmt.Println("response Status:", resp.Status)
    fmt.Println("response Headers:", resp.Header)
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println("response Body:", string(body))
}
OneOfOne
  • 81,080
  • 16
  • 165
  • 166
  • 1
    now it have panics on playground. May be you shold fix or update something? – Altenrion Jan 04 '17 at 16:02
  • 10
    @Altenrion it can't work on the playground, I just used it to paste the code, you can't open external connections from it. – OneOfOne Jan 08 '17 at 11:04
  • @Altenrion may this [link](https://stackoverflow.com/questions/30664557/http-get-returns-protocol-not-available-error) help you – Ardi Nusawan Mar 28 '18 at 09:34
  • 13
    @Altenrion +1 for solid band name suggestion. – Charlie Schliesser Mar 30 '18 at 19:10
  • 9
    Just a warning, don't forget that by default the golang http client never times out, so for the real world, best to set something along the lines of `client.Timeout = time.Second * 15` – svarlamov Aug 03 '18 at 04:30
  • 1
    Can this be updated to gather / inspect all it's errors? This is (for me, at least) the top result on google for making post requests in Go, and it's a good answer, but I see a ton of example code that just ignores errors, and I think it encourages bad practice in newbies. Then again, if anyone regularly ignores errors, I suppose they'll learn why not to eventually, but why not encourage good practice to begin with? – K. Rhoda Oct 17 '18 at 20:20
  • how can i send json inside json? – Vani Polnedi Dec 09 '19 at 12:04
  • `json.RawMessage` – OneOfOne Dec 09 '19 at 20:42
128

you can just use post to post your json.

values := map[string]string{"username": username, "password": password}

jsonValue, _ := json.Marshal(values)

resp, err := http.Post(authAuthenticatorUrl, "application/json", bytes.NewBuffer(jsonValue))
Sjon
  • 4,605
  • 6
  • 24
  • 42
gaozhidf
  • 1,924
  • 1
  • 16
  • 13
  • 3
    I get this error : `cannot use jsonValue (type []byte) as type io.Reader in argument to http.Post: []byte does not implement io.Reader (missing Read method)` – Mandar Vaze Dec 30 '16 at 12:52
  • @MandarVaze i think you may get wrong `io.Reader` for `http.Post`, and bytes.NewBuffer() works well in my code – gaozhidf Jan 02 '17 at 08:03
  • 2
    I'm on go 1.7, if it matters. The code listed by @OneOfOne works (which also uses `bytes.NewBuffer()` but uses `http.NewRequest` instead of `http.Post`) – Mandar Vaze Jan 04 '17 at 07:11
  • 2
    According to https://golang.org/pkg/net/http/#Post, "Caller should close `resp.Body` when done reading from it. If the provided body is an `io.Closer`, it is closed after the request." How can I tell, as a Go newbie, if the body is an `io.Closer`, or in other words, if this example is safe? – Dennis Mar 04 '20 at 14:54
  • The only limitation with this approach is that you can't set custom headers :/ – invinciblycool Mar 25 '21 at 09:51
34

If you already have a struct.

import (
    "bytes"
    "encoding/json"
    "io"
    "net/http"
    "os"
)

// .....

type Student struct {
    Name    string `json:"name"`
    Address string `json:"address"`
}

// .....

body := &Student{
    Name:    "abc",
    Address: "xyz",
}

payloadBuf := new(bytes.Buffer)
json.NewEncoder(payloadBuf).Encode(body)
req, _ := http.NewRequest("POST", url, payloadBuf)

client := &http.Client{}
res, e := client.Do(req)
if e != nil {
    return e
}

defer res.Body.Close()

fmt.Println("response Status:", res.Status)
// Print the body to the stdout
io.Copy(os.Stdout, res.Body)

Full gist.

Ninh Pham
  • 4,298
  • 35
  • 26
14

In addition to standard net/http package, you can consider using my GoRequest which wraps around net/http and make your life easier without thinking too much about json or struct. But you can also mix and match both of them in one request! (you can see more details about it in gorequest github page)

So, in the end your code will become like follow:

func main() {
    url := "http://restapi3.apiary.io/notes"
    fmt.Println("URL:>", url)
    request := gorequest.New()
    titleList := []string{"title1", "title2", "title3"}
    for _, title := range titleList {
        resp, body, errs := request.Post(url).
            Set("X-Custom-Header", "myvalue").
            Send(`{"title":"` + title + `"}`).
            End()
        if errs != nil {
            fmt.Println(errs)
            os.Exit(1)
        }
        fmt.Println("response Status:", resp.Status)
        fmt.Println("response Headers:", resp.Header)
        fmt.Println("response Body:", body)
    }
}

This depends on how you want to achieve. I made this library because I have the same problem with you and I want code that is shorter, easy to use with json, and more maintainable in my codebase and production system.

A-letubby
  • 6,440
  • 8
  • 34
  • 46
  • If GoRequest wraps net/http. Is it possible to add this to disable the Insecure certificate for TLS ? `tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }` – user1513388 Oct 05 '15 at 17:58
  • 49
    @user1513388 It's always a terrible idea to contribute code examples of skipping TLS verification in any scenario in any language... you accidentally perpetuate a lot copy/paste "workarounds" by neophytes who visit StackOverflow and don't understand the nature of why _fixing_ TLS errors is crucial. Either fix your certificate import path (if using self-signed for testing, import those) or fix your machine's certificate chain, or find out why your server is presenting an invalid certificate that can't be verified by your client. – Mike Atlas Oct 25 '15 at 22:27
  • 2
    One thing I don't exactly like about this answer is the way it composes the JSON object, which is potentially exploitable via injection. A better way would be to compose an object and then transform it to JSON (with the proper escaping). – John White Mar 01 '19 at 18:33
  • @JohnWhite I agree, feels very ruby/js/pythonic – Rambatino Jun 14 '19 at 13:09
0

Example post request for http or https

    //Encode the data
       postBody, _ := json.Marshal(map[string]string{
          "name":  "Test",
          "email": "Test@Test.com",
       })
       responseBody := bytes.NewBuffer(postBody)
    //Leverage Go's HTTP Post function to make request
       resp, err := http.Post("https://postman-echo.com/post", "application/json", responseBody)
    //Handle Error
       if err != nil {
          log.Fatalf("An Error Occured %v", err)
       }
       defer resp.Body.Close()
    //Read the response body
       body, err := ioutil.ReadAll(resp.Body)
       if err != nil {
          log.Fatalln(err)
       }
       sb := string(body)
       log.Printf(sb)
mesutpiskin
  • 1,198
  • 1
  • 15
  • 23
0

If you have a lot of data to send, you can use a pipe:

package main

import (
   "encoding/json"
   "io"
   "net/http"
)

func main() {
   m := map[string]int{"SNG_ID": 75498415}
   r, w := io.Pipe()
   go func() {
      json.NewEncoder(w).Encode(m)
      w.Close()
   }()
   http.Post("https://stackoverflow.com", "application/json", r)
}

https://golang.org/pkg/io#Pipe

Steven Penny
  • 82,115
  • 47
  • 308
  • 348
0

Use io.Pipe for large request bodies as mentioned in another answer. This approach avoids building the entire request body in memory by streaming the data from the JSON encoder to the network.

This answer builds on the other answer by showing how to handle errors. Always handle errors!

  • Use the pipe's CloseWithError function to propagate encoding errors back to error returned from http.Post.
  • Handle the error returned from http.Post
  • Close the response body.

Here's the code:

r, w := io.Pipe()
go func() {
    w.CloseWithError(json.NewEncoder(w).Encode(data))
}()
resp, err := http.Post(url, r)
if err != nil {
    // Adjust error handling here to meet application requrirements.
    log.Fatal(err)
}
defer resp.Body.Close()
// Use the response here.
Jon Nickles
  • 1,709
  • 3
  • 12