Utwórz żądanie POST zakodowane w adresie URL, używając `http.NewRequest (…)`

97

Chcę wysłać żądanie POST do interfejsu API wysyłającego moje dane jako application/x-www-form-urlencodedtyp zawartości. Ze względu na to, że muszę zarządzać nagłówkami żądań, używam http.NewRequest(method, urlStr string, body io.Reader)metody do tworzenia żądania. W przypadku tego żądania POST dołączam zapytanie o dane do adresu URL i zostawiam treść pustą, coś takiego:

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

Kiedy odpowiadam, zawsze otrzymuję 400 BAD REQUEST. Uważam, że problem zależy od mojego żądania, a interfejs API nie rozumie, który ładunek wysyłam. Jestem świadomy takich metod, jak Request.ParseForm, ale nie bardzo wiem, jak ich użyć w tym kontekście. Może brakuje mi jakiegoś dalszego nagłówka, może jest lepszy sposób na wysłanie ładunku jako application/jsontypu za pomocą bodyparametru?

Fernando Á.
źródło

Odpowiedzi:

187

Ładunek zakodowany w adresie URL należy podać w bodyparametrze http.NewRequest(method, urlStr string, body io.Reader)metody jako typ implementujący io.Readerinterfejs.

Na podstawie przykładowego kodu:

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.Statusjest w 200 OKten sposób.

Fernando Á.
źródło
2
a co jeśli nie chcę przesyłać żadnych danych? Jeśli wyślę jakieś fikcyjne dane zamiast `bytes.NewBufferString (data.Encode ())`, czy to zadziała?
Aditya Peshave
Spróbowałbym wysłać pusty * Bufor: np. bPodanyvar b bytes.Buffer
Fernando Á.
4
Nie musisz ustawiać Content-Lenghtnagłówka, jak http.NewRequestto już robisz .
dvdplm
12
Myślę, że możemy użyć strings.NewReader(data.Encode())(bardziej wydajnie) zamiast bytes.NewBufferString(data.Encode()). W func NewReader (s string) * Reader jest napisane: „NewReader zwraca nowy odczyt czytnika z s. Jest podobny do bajtów.NewBufferString, ale jest bardziej wydajny i tylko do odczytu”.
Liyang Chen
1
data.Setnależy używać zarówno dla, jak namei surnamezamiast data.Add. Ustawia wartość klucza, zamiast dołączać inną wartość do tego samego klucza, jak to data.Addma miejsce. Adddziała też, ale nie trzeba append(v[key], value)opróżniać plastra.
metalim