Faites une requête POST encodée en URL en utilisant `http.NewRequest (…)`

96

Je souhaite envoyer une requête POST à ​​une API qui envoie mes données en tant que application/x-www-form-urlencodedtype de contenu. Étant donné que je dois gérer les en-têtes de demande, j'utilise la http.NewRequest(method, urlStr string, body io.Reader)méthode pour créer une demande. Pour cette demande POST, j'ajoute ma requête de données à l'URL et laisse le corps vide, quelque chose comme ceci:

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

En réponse, j'obtiens toujours un 400 BAD REQUEST. Je pense que le problème repose sur ma demande et que l'API ne comprend pas la charge utile que je publie. Je connais des méthodes comme Request.ParseForm, je ne sais pas vraiment comment l'utiliser dans ce contexte. Est-ce que je manque peut-être un autre en-tête, peut-être existe-t-il un meilleur moyen d'envoyer la charge utile en tant que application/jsontype à l'aide du bodyparamètre?

Fernando Á.
la source

Réponses:

184

La charge utile encodée en URL doit être fournie sur le bodyparamètre de la http.NewRequest(method, urlStr string, body io.Reader)méthode, en tant que type qui implémente l' io.Readerinterface.

Basé sur l'exemple de code:

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.Statusest de 200 OKcette façon.

Fernando Á.
la source
2
et si je ne veux pas envoyer de données ?? Si j'envoie des données factices à la place de `bytes.NewBufferString (data.Encode ())`, cela fonctionnera-t-il?
Aditya Peshave
J'essaierais d'envoyer un * Buffer vide: par exemple bdonnévar b bytes.Buffer
Fernando Á.
4
Vous n'avez pas besoin de définir l'en- Content-Lenghttête, comme http.NewRequestc'est déjà le cas.
dvdplm
12
Je pense que nous pouvons utiliser strings.NewReader(data.Encode())(plus efficace) au lieu de bytes.NewBufferString(data.Encode()). À func NewReader (s string) * Reader , il dit "NewReader renvoie une nouvelle lecture de Reader à partir de s. Il est similaire à bytes.NewBufferString mais plus efficace et en lecture seule."
Liyang Chen
1
data.Setdoit être utilisé pour les deux nameet surname, au lieu de data.Add. Il définit la valeur de la clé, au lieu d'ajouter une autre valeur à la même clé, comme le data.Addfait. Addfonctionne aussi, mais ne permet pas append(v[key], value)de vider la tranche.
metalim