ChatGPT解决这个技术问题 Extra ChatGPT

How do I send a JSON string in a POST request in Go

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.

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?
why are you unmarshaling the json if you want to send json?
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, 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.

O
OneOfOne

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))
}

now it have panics on playground. May be you shold fix or update something?
@Altenrion it can't work on the playground, I just used it to paste the code, you can't open external connections from it.
@Altenrion +1 for solid band name suggestion.
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
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?
S
Sjon

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))

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)
@MandarVaze i think you may get wrong io.Reader for http.Post, and bytes.NewBuffer() works well in my code
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)
According to 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?
The only limitation with this approach is that you can't set custom headers :/
N
Ninh Pham

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.


A
A-letubby

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.


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 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.
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).
@JohnWhite I agree, feels very ruby/js/pythonic
3
3 revs user13631587

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))
}()

// Ensure that read side of pipe is closed. This
// unblocks goroutine in scenario where http.Post
// errors out before reading the entire request body.
defer r.Close()

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.

m
mesutpiskin

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)

Z
Zombo

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


H
Highdeger

if you want to do it like that, you need to use this map for unmarshalling json string.

var data map[string]interface{}

but if you need to change the json each time and to make initialization of your requst body more convenient, you can use this map for creating json body.

var bodyJsonMap map[string]interface{}{
    "key1": val1,
    "key2": val2,
    ...
}

Then marshal it to a json-string.