Realice una solicitud POST con codificación URL mediante "http.NewRequest (…)"

96

Quiero realizar una solicitud POST a una API que envíe mis datos como un application/x-www-form-urlencodedtipo de contenido. Debido al hecho de que necesito administrar los encabezados de la solicitud, estoy usando el http.NewRequest(method, urlStr string, body io.Reader)método para crear una solicitud. Para esta solicitud POST, agrego mi consulta de datos a la URL y dejo el cuerpo vacío, algo como esto:

package main

import (
    "bytes"
    "fmt"
    "net/http"
    "net/url"
    "strconv"
)

func main() {
    apiUrl := "https://api.com"
    resource := "/user/"
    data := url.Values{}
    data.Set("name", "foo")
    data.Add("surname", "bar")

    u, _ := url.ParseRequestURI(apiUrl)
    u.Path = resource
    u.RawQuery = data.Encode()
    urlStr := fmt.Sprintf("%v", u) // "https://api.com/user/?name=foo&surname=bar"

    client := &http.Client{}
    r, _ := http.NewRequest("POST", urlStr, nil)
    r.Header.Add("Authorization", "auth_token=\"XXXXXXX\"")
    r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
    r.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))

    resp, _ := client.Do(r)
    fmt.Println(resp.Status)
}

Como respondo, siempre obtengo un 400 BAD REQUEST. Creo que el problema se basa en mi solicitud y la API no comprende qué carga útil estoy publicando. Sin Request.ParseFormembargo, soy consciente de métodos como , no estoy seguro de cómo usarlo en este contexto. Tal vez me falte un encabezado adicional, tal vez ¿hay una mejor manera de enviar la carga útil como un application/jsontipo usando el bodyparámetro?

Fernando Á.
fuente

Respuestas:

184

La carga útil codificada en URL debe proporcionarse en el bodyparámetro del http.NewRequest(method, urlStr string, body io.Reader)método, como un tipo que implementa la io.Readerinterfaz.

Basado en el código de muestra:

package main

import (
    "fmt"
    "net/http"
    "net/url"
    "strconv"
    "strings"
)

func main() {
    apiUrl := "https://api.com"
    resource := "/user/"
    data := url.Values{}
    data.Set("name", "foo")
    data.Set("surname", "bar")

    u, _ := url.ParseRequestURI(apiUrl)
    u.Path = resource
    urlStr := u.String() // "https://api.com/user/"

    client := &http.Client{}
    r, _ := http.NewRequest(http.MethodPost, urlStr, strings.NewReader(data.Encode())) // URL-encoded payload
    r.Header.Add("Authorization", "auth_token=\"XXXXXXX\"")
    r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
    r.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))

    resp, _ := client.Do(r)
    fmt.Println(resp.Status)
}

resp.Statuses de 200 OKesta manera.

Fernando Á.
fuente
2
¿y si no quiero enviar ningún dato? Si envío cualquier dato ficticio en lugar de `bytes.NewBufferString (data.Encode ())`, ¿funcionará?
Aditya Peshave
Intentaría enviar un * Buffer vacío: por ejemplo, bdadovar b bytes.Buffer
Fernando Á.
4
No es necesario configurar el Content-Lenghtencabezado, como http.NewRequestya lo hace.
DVDplm
12
Creo que podemos usar strings.NewReader(data.Encode())(más eficiente) en lugar de bytes.NewBufferString(data.Encode()). En func NewReader (s string) * Reader , dice "NewReader devuelve una nueva lectura de Reader de s. Es similar a bytes.NewBufferString pero más eficiente y de solo lectura".
Liyang Chen
1
data.Setdebe usarse para ambos namey surname, en lugar de data.Add. Establece el valor de la clave, en lugar de agregar otro valor a la misma clave, como lo data.Addhace. Addtambién funciona, pero no es necesario append(v[key], value)vaciar el segmento.
metalim