Como envio uma string JSON em uma solicitação POST no Go

244

Eu tentei trabalhar com o Apiary e fiz um modelo universal para enviar JSON para servidor de simulação e ter este código:

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

}

Esse código não envia JSON corretamente, mas não sei por que. A cadeia JSON pode ser diferente em cada chamada. Eu não posso usar Structpara isso.

Ladislav Prskavec
fonte
Não estou familiarizado com algumas das bibliotecas que você usa, mas pelo que entendi, você está tentando enviar um mapa de Jsons. Por que você simplesmente não envia a string com o json?
Topo
1
por que você está desempacotando o json se você deseja enviar o json?
JimB 27/06
Uma pequena dica, você pode criar sua mensagem como uma interface [string] struct ou map {} para adicionar todos os valores desejados e, em seguida, usar json.Marshall para converter o mapa ou struct em um json.
Topo
@topo, eu procurei no código fonte do cochilando, se a carga útil estiver definida, eles o chamam json.Marshall, não sei por que não estava funcionando para ele.
OneOfOne 27/06

Respostas:

501

Eu não estou familiarizado com cochilos, mas usar o net/httppacote de Golang funciona bem ( 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
fonte
1
agora tem pânico no parquinho. Pode ser que você conserte ou atualize alguma coisa?
Altenrion
9
@ Altenrion não pode funcionar no playground, eu apenas usei para colar o código, você não pode abrir conexões externas a partir dele.
OneOfOne
8
@ Altenrion +1 para sugestão de nome de banda sólida.
Charlie Schliesser
8
Apenas um aviso, não se esqueça que, por padrão o cliente golang http nunca mais vezes fora, então para o mundo real, melhor para definir algo ao longo das linhas declient.Timeout = time.Second * 15
svarlamov
1
Isso pode ser atualizado para reunir / inspecionar todos os seus erros? Este é (pelo menos para mim) o melhor resultado no Google para fazer solicitações de postagem no Go, e é uma boa resposta, mas vejo uma tonelada de código de exemplo que apenas ignora erros e acho que incentiva práticas inadequadas para iniciantes. Por outro lado, se alguém ignora regularmente os erros, suponho que eles aprenderão por que não eventualmente, mas por que não incentivar boas práticas para começar?
K. Rhoda
103

você pode apenas usar postpara postar seu json.

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

jsonValue, _ := json.Marshal(values)

resp, err := http.Post(authAuthenticatorUrl, "application/json", bytes.NewBuffer(jsonValue))
gaozhidf
fonte
3
Eu recebo este erro: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
@MandarVaze eu acho que você pode obter errado io.Readerpara http.Post, e bytes.NewBuffer () funciona bem no meu código
gaozhidf
1
Estou indo em 1,7, se isso importa. O código listado por obras @OneOfOne (que também usa bytes.NewBuffer()mas usos http.NewRequestem vez de http.Post)
Mandar Vaze
2
De acordo com golang.org/pkg/net/http/#Post , "O chamador deve fechar resp.Bodyquando terminar de ler. Se o corpo fornecido for um io.Closer, ele será fechado após a solicitação". Como posso dizer, como novato no Go, se o corpo é um io.Closer, ou em outras palavras, se este exemplo é seguro?
Dennis
14

Se você já possui uma estrutura.

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

// .....

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

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

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)

Essência completa .

Ninh Pham
fonte
12

Além do pacote padrão net / http, você pode usar o GoRequest, que envolve net / http e facilita sua vida sem pensar muito em json ou struct. Mas você também pode misturar e combinar os dois em uma solicitação! (você pode ver mais detalhes sobre isso na página do gorequest github)

Portanto, no final, seu código se tornará o seguinte:

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

Isso depende de como você deseja alcançar. Criei esta biblioteca porque tenho o mesmo problema com você e quero um código mais curto, fácil de usar com o json e mais sustentável em minha base de código e sistema de produção.

A-letubby
fonte
Se o GoRequest agrupar net / http. É possível adicionar isso para desativar o certificado inseguro para TLS? tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }
user1513388
46
@ user1513388 É sempre uma péssima ideia contribuir com exemplos de código para ignorar a verificação TLS em qualquer cenário em qualquer idioma ... você acidentalmente perpetua muitas cópias / colas "soluções alternativas" por neófitos que visitam o StackOverflow e não entendem a natureza do porquê da correção Erros TLS é crucial. Corrija o caminho de importação do certificado (se estiver usando autoassinado para teste, importe-o) ou corrija a cadeia de certificados da sua máquina ou descubra por que o servidor está apresentando um certificado inválido que não pode ser verificado pelo seu cliente.
Mike Atlas
1
Uma coisa que não gosto exatamente sobre essa resposta é a maneira como ela compõe o objeto JSON, que é potencialmente explorável por injeção. Uma maneira melhor seria compor um objeto e depois transformá-lo em JSON (com o escape apropriado).
John John
@JohnWhite Concordo, se sente muito rubi / js / pythônico
Rambatino