Сделайте запрос POST в кодировке URL, используя `http.NewRequest (…)`

98

Я хочу сделать POST-запрос к API, отправляющему мои данные как application/x-www-form-urlencodedтип содержимого. Из-за того, что мне нужно управлять заголовками запросов, я использую этот http.NewRequest(method, urlStr string, body io.Reader)метод для создания запроса. Для этого запроса POST я добавляю свой запрос данных к URL-адресу и оставляю тело пустым, примерно так:

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

Когда я отвечаю, я всегда получаю 400 BAD REQUEST. Я считаю, что проблема связана с моим запросом, и API не понимает, какие данные я публикую. Я знаю такие методы, как Request.ParseForm, но не совсем уверен, как их использовать в этом контексте. Может быть, мне не хватает какого-то дополнительного заголовка, может быть, есть лучший способ отправить полезную нагрузку в виде application/jsonтипа с использованием bodyпараметра?

Фернандо Á.
источник

Ответы:

189

Полезные данные в кодировке URL должны быть указаны в bodyпараметре http.NewRequest(method, urlStr string, body io.Reader)метода как тип, реализующий io.Readerинтерфейс.

На основе примера кода:

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это 200 OKтаким образом.

Фернандо Á.
источник
2
что, если я не хочу отправлять данные ?? Если я отправлю какие-либо фиктивные данные вместо bytes.NewBufferString (data.Encode ()), это будет работать?
Адитья Пешаве
Я бы попытался отправить пустой * буфер: например, bданныйvar b bytes.Buffer
Фернандо Á.
4
Вам не нужно устанавливать Content-Lenght заголовок, как http.NewRequestэто уже .
dvdplm 06
12
Я думаю, что мы можем использовать strings.NewReader(data.Encode())(более эффективно) вместо bytes.NewBufferString(data.Encode()). В func NewReader (s string) * Reader он говорит: «NewReader возвращает новое чтение Reader из s. Оно похоже на bytes.NewBufferString, но более эффективно и доступно только для чтения».
Лиянг Чен
1
data.Setследует использовать для обоих nameи surnameвместо data.Add. Он устанавливает значение ключа вместо добавления другого значения к тому же ключу, как это data.Addделает. Addтоже работает, но не очищает append(v[key], value)срез.
metalim