Faça uma solicitação POST codificada por URL usando `http.NewRequest (…)`

94

Quero fazer uma solicitação POST para uma API enviando meus dados como um application/x-www-form-urlencodedtipo de conteúdo. Devido ao fato de que preciso gerenciar os cabeçalhos da solicitação, estou usando o http.NewRequest(method, urlStr string, body io.Reader)método para criar uma solicitação. Para esta solicitação POST, acrescento minha consulta de dados ao URL e deixo o corpo vazio, algo assim:

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

Conforme eu respondo, sempre recebo um 400 BAD REQUEST. Acredito que o problema depende da minha solicitação e a API não entende qual carga útil estou postando. Estou ciente de métodos como Request.ParseForm, mas não tenho certeza de como usá-lo neste contexto. Talvez eu esteja faltando algum cabeçalho adicional, talvez haja uma maneira melhor de enviar carga útil como um application/jsontipo usando o bodyparâmetro?

Fernando Á.
fonte

Respostas:

182

A carga útil codificada por URL deve ser fornecida no bodyparâmetro do http.NewRequest(method, urlStr string, body io.Reader)método, como um tipo que implementa a io.Readerinterface.

Com base no código de amostra:

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.Statusé por 200 OKaqui.

Fernando Á.
fonte
2
e se eu não quiser enviar nenhum dado ?? Se eu enviar algum dado fictício no lugar de `bytes.NewBufferString (data.Encode ())`, vai funcionar?
Aditya Peshave
Eu tentaria enviar um * Buffer vazio: por exemplo, bdadovar b bytes.Buffer
Fernando Á.
4
Você não precisa definir o Content-Lenghtcabeçalho, como http.NewRequestjá faz isso.
dvdplm
12
Acho que podemos usar strings.NewReader(data.Encode())(mais eficiente) em vez de bytes.NewBufferString(data.Encode()). Em func NewReader (s string) * Reader , ele diz "NewReader retorna uma nova leitura de s. É semelhante a bytes.NewBufferString, mas mais eficiente e somente leitura."
Liyang Chen,
1
data.Setdeve ser usado para namee surname, em vez de data.Add. Ele define o valor da chave, em vez de anexar outro valor à mesma chave, como data.Addfaz. Addfunciona também, mas é desnecessário append(v[key], value)para esvaziar a fatia.
metalim