Как правильно выполнить GET-запрос и собрать QueryString в Go?
Правильный способ: использование net/url пакета
В Go есть встроенный пакет net/url, который предоставляет все необходимые инструменты для безопасной и удобной сборки QueryString. Основная идея — создать объект url.URL, задать его поля (схему, хост, путь), а затем использовать метод Query() для получения объекта url.Values, который представляет собой карту (map) параметров. После добавления параметров нужно закодировать их в строку запроса через метод Encode().
Пошаговый пример
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
)
func main() {
// 1. Создаём базовый URL
baseURL, _ := url.Parse("https://api.example.com/search")
// 2. Получаем объект для параметров query string
params := url.Values{}
params.Add("q", "golang & go") // специальные символы будут экранированы
params.Add("page", "1")
params.Add("limit", "10")
// 3. Кодируем параметры в строку запроса
baseURL.RawQuery = params.Encode()
// 4. Выполняем GET-запрос
resp, err := http.Get(baseURL.String())
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
// URL будет: https://api.example.com/search?limit=10&page=1&q=golang+%26+go
}
Что здесь происходит?
url.Parse()— разбирает строку URL на структуруurl.URL. Это удобно, если у вас уже есть базовый URL.url.Values{}— это аналогmap[string][]string. Каждый ключ может иметь несколько значений (например, для массива параметров).params.Add(key, value)— добавляет параметр. Если ключ уже существует, значение добавляется в список (не перезаписывается).params.Set(key, value)— если нужно заменить все значения ключа на новое.params.Encode()— кодирует все параметры в строку запроса с правильным экранированием. Пробелы становятся+или%20, специальные символы —%XX.baseURL.RawQuery— присваивает закодированную строку запроса URL.
Более лаконичный вариант (без url.Parse)
Если вам нужно просто собрать URL с нуля, можно использовать url.URL напрямую:
func main() {
params := url.Values{}
params.Add("q", "golang & go")
params.Add("page", "1")
u := &url.URL{
Scheme: "https",
Host: "api.example.com",
Path: "/search",
RawQuery: params.Encode(),
}
resp, _ := http.Get(u.String())
// ...
}
Что делать с ответом? (Совет для новичков)
Не забывайте проверять статус ответа и закрывать тело:
if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
log.Fatalf("API error: %s", string(body))
}
Также в Go 1.16+ рекомендуется использовать io.ReadAll вместо ioutil.ReadAll, но ioutil пока работает.
Итог
Никогда не склеивайте URL вручную — это источник багов и уязвимостей. Используйте net/url — он стандартный, легковесный и полностью покрывает все потребности.
Альтернативный подход: использование сторонней библиотеки go-querystring
Если вы работаете с большим количеством запросов и хотите автоматически преобразовывать структуры Go в QueryString, можно использовать библиотеку github.com/google/go-querystring/query. Она позволяет определить параметры запроса как поля структуры с тегами url.
package main
import (
"fmt"
"net/http"
"net/url"
"github.com/google/go-querystring/query"
)
type SearchParams struct {
Query string `url:"q"`
Page int `url:"page"`
Limit int `url:"limit"`
}
func main() {
params := SearchParams{
Query: "golang & go",
Page: 1,
Limit: 10,
}
v, _ := query.Values(params)
baseURL, _ := url.Parse("https://api.example.com/search")
baseURL.RawQuery = v.Encode()
resp, _ := http.Get(baseURL.String())
defer resp.Body.Close()
fmt.Println("URL:", baseURL.String())
// Результат: https://api.example.com/search?limit=10&page=1&q=golang+%26+go
}
Плюсы и минусы
- Плюсы: код становится чище, если параметров много; теги позволяют задавать имена полей, отличные от Go-переменных; автоматическая сериализация типов (int, bool, slice).
- Минусы: нужно подключать внешнюю зависимость; для простых запросов избыточно.
Рекомендую для проектов, где много разных API-запросов. Для единичных случаев используйте стандартный net/url из первого ответа.