¿Cómo envío una cadena JSON en una solicitud POST en Go?

244

Intenté trabajar con Apiary e hice una plantilla universal para enviar JSON al servidor falso y tener 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())

}

Este código no envía JSON correctamente, pero no sé por qué. La cadena JSON puede ser diferente en cada llamada. No puedo usar Structpara esto.

Ladislav Prskavec
fuente
No estoy familiarizado con algunas de las bibliotecas que usa, pero según tengo entendido, está tratando de enviar un mapa de Jsons. ¿Por qué no envías la cadena con el json?
Topo
1
¿por qué estás desarmando a json si quieres enviar json?
JimB
Un pequeño consejo: puede crear su mensaje como una estructura o interfaz de mapa [cadena] {} para agregar todos los valores que desee y luego usar json.Marshall para convertir el mapa o estructura en un json.
Topo
@topo, busqué el código fuente de la siesta, si la carga útil está configurada, lo invocan json.Marshall, no estoy seguro de por qué no estaba funcionando para él.
OneOfOne

Respuestas:

501

No estoy familiarizado con la siesta, pero usar el net/httppaquete de Golang funciona bien (área de juegos ):

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))
}
Uno de uno
fuente
1
ahora tiene pánico en el patio de recreo. ¿Puedes arreglar o actualizar algo?
Altenrion
99
@Altenrion no puede funcionar en el patio de recreo, solo lo usé para pegar el código, no puedes abrir conexiones externas desde él.
OneOfOne
8
@Altenrion +1 para sugerencia de nombre de banda sólida.
Charlie Schliesser
8
Solo una advertencia, no olvide que, de manera predeterminada, el cliente http de Golang nunca agota el tiempo, por lo que para el mundo real, es mejor configurar algo en la línea declient.Timeout = time.Second * 15
svarlamov
1
¿Se puede actualizar para recopilar / inspeccionar todos sus errores? Este es (para mí, al menos) el mejor resultado en Google para realizar solicitudes de publicación en Go, y es una buena respuesta, pero veo un montón de código de ejemplo que simplemente ignora los errores, y creo que fomenta las malas prácticas en los novatos. Por otra parte, si alguien ignora los errores regularmente, supongo que aprenderán por qué no hacerlo eventualmente, pero ¿por qué no fomentar las buenas prácticas para comenzar?
K. Rhoda
103

puedes usar postpara publicar tu json.

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

jsonValue, _ := json.Marshal(values)

resp, err := http.Post(authAuthenticatorUrl, "application/json", bytes.NewBuffer(jsonValue))
gaozhidf
fuente
3
Me sale este 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
@MandarVaze Creo que puede equivocarse io.Readerpara http.Post, y bytes.NewBuffer () funciona bien en mi código
gaozhidf
1
Estoy en ir 1.7, si importa. El código listado por @OneOfOne funciona (que también usa bytes.NewBuffer()pero usa en http.NewRequestlugar de http.Post)
Mandar Vaze
2
De acuerdo con golang.org/pkg/net/http/#Post , "La persona que llama debe cerrar resp.Bodycuando termine de leerla. Si el cuerpo proporcionado es un io.Closer, se cierra después de la solicitud". ¿Cómo puedo saber, como novato en Go, si el cuerpo es io.Closero, en otras palabras, si este ejemplo es seguro?
Dennis
14

Si ya tienes una estructura.

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)

Esencia completa .

Ninh Pham
fuente
12

Además del paquete estándar net / http, puede considerar usar mi GoRequest que se ajusta a net / http y le facilita la vida sin pensar demasiado en json o struct. ¡Pero también puedes mezclar y combinar ambos en una sola solicitud! (puede ver más detalles al respecto en la página de gorequest github)

Entonces, al final su código se convertirá en el siguiente:

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

Esto depende de cómo quieras lograrlo. Hice esta biblioteca porque tengo el mismo problema contigo y quiero un código más corto, fácil de usar con json y más fácil de mantener en mi base de código y sistema de producción.

A-letubby
fuente
Si GoRequest envuelve net / http. ¿Es posible agregar esto para deshabilitar el certificado Insecure para TLS? tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }
user1513388
46
@ user1513388 Siempre es una idea terrible contribuir con ejemplos de código para omitir la verificación TLS en cualquier escenario en cualquier idioma ... accidentalmente perpetúa muchas "soluciones" de copiar / pegar por neófitos que visitan StackOverflow y no entienden la naturaleza de por qué arreglar Los errores de TLS son cruciales. Arregle la ruta de importación de su certificado (si usa autofirmado para la prueba, impórtelos) o arregle la cadena de certificados de su máquina, o descubra por qué su servidor presenta un certificado no válido que su cliente no puede verificar.
Mike Atlas
1
Una cosa que no me gusta exactamente de esta respuesta es la forma en que compone el objeto JSON, que es potencialmente explotable mediante inyección. Una mejor manera sería componer un objeto y luego transformarlo a JSON (con el escape adecuado).
John White
@JohnWhite Estoy de acuerdo, se siente muy ruby ​​/ js / pythonic
Rambatino