Как задать заголовки в HTTP GET запросе на Go? Полное объяснение с кодом
Вы когда-нибудь пытались отправить HTTP GET запрос на сервер, а он возвращал ошибку авторизации? Или, может быть, API игнорировал ваш запрос, хотя вы точно знали, что он должен работать? В 90% случаев проблема кроется в заголовках HTTP. Это как пропуск на вечеринку: без правильного документа вас просто не пустят.
В этой статье мы разберем, как в Go (Golang) профессионально управлять заголовками в HTTP GET запросах. Вы узнаете не только базовый синтаксис, но и типичные ловушки, в которые попадают даже опытные разработчики. Поехали!
Почему заголовки — это важно?
Представьте, что HTTP-запрос — это конверт с письмом. Сам конверт (тело запроса) может быть пустым, но вот информация на нем (заголовки) решает всё:
- Content-Type — формат данных (JSON, XML, Form)
- Authorization — ключ доступа к API (Bearer token, Basic Auth)
- User-Agent — идентификация клиента (чтобы вас не заблокировали как бота)
- Accept — какой формат ответа вы ожидаете
Без правильных заголовков сервер просто не поймет, что вы от него хотите.
Способ 1: Старый добрый http.Get (и почему он не подходит)
Многие новички в Go начинают с http.Get(). Это удобно для тестов, но у этого метода есть огромный минус — нельзя задать заголовки.
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
// Так нельзя добавить заголовки!
resp, err := http.Get("https://api.example.com/data")
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
Этот код просто шлет «голый» запрос. Если API требует токен авторизации — получите 401 Unauthorized. Для реальных проектов такой подход не годится.
Способ 2: Создаем собственный Request (правильный путь)
В Go для тонкой настройки запросов используется конструктор http.NewRequest(). Он возвращает объект *http.Request, с которым можно делать всё что угодно, включая установку заголовков.
Вот базовая структура:
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
// 1. Создаем URL
url := "https://api.github.com/users/golang"
// 2. Создаем новый GET запрос
req, err := http.NewRequest("GET", url, nil)
if err != nil {
panic(err)
}
// 3. Устанавливаем заголовки
req.Header.Set("User-Agent", "MyGoApp/1.0")
req.Header.Set("Accept", "application/json")
// 4. Отправляем запрос через HTTP клиент
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
Обратите внимание: мы используем req.Header.Set(), а не req.Header.Add(). Разница важна:
- Set — заменяет существующее значение (если такой заголовок уже есть)
- Add — добавляет еще одно значение к уже существующему (создает дубликат)
Для большинства случаев используйте Set — это безопаснее.
Способ 3: Работа с авторизацией (Bearer Token)
Самый частый сценарий — передача JWT токена. Вот как это делается правильно:
package main
import (
"fmt"
"net/http"
)
func main() {
token := "your_jwt_token_here"
url := "https://api.example.com/protected"
req, _ := http.NewRequest("GET", url, nil)
// Устанавливаем Authorization заголовок
req.Header.Set("Authorization", "Bearer "+token)
// Дополнительные заголовки
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Request-ID", "unique-request-id-123")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Ошибка запроса:", err)
return
}
defer resp.Body.Close()
fmt.Println("Статус ответа:", resp.Status)
}
Кстати, о Content-Type: для GET запросов он технически не нужен (тела нет), но многие API его все равно проверяют. Лучше всегда указывать.
Способ 4: Использование кастомного HTTP клиента с таймаутами
В реальных проектах вы не захотите ждать ответа вечность. Добавим таймауты и переиспользуемый клиент:
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
// Создаем клиент с таймаутом 10 секунд
client := &http.Client{
Timeout: 10 * time.Second,
}
url := "https://jsonplaceholder.typicode.com/posts/1"
req, _ := http.NewRequest("GET", url, nil)
// Массовая установка заголовков
headers := map[string]string{
"User-Agent": "MyApp/2.0",
"Accept-Language": "ru-RU,ru;q=0.9",
"Cache-Control": "no-cache",
}
for key, value := range headers {
req.Header.Set(key, value)
}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Ошибка:", err)
return
}
defer resp.Body.Close()
fmt.Printf("Успех! Статус: %s\n", resp.Status)
}
Зачем нужен отдельный клиент? Потому что http.DefaultClient не имеет таймаута. Если сервер зависнет, ваша программа зависнет навсегда. Кастомный клиент решает эту проблему.
Типичные ошибки и как их избежать
Даже опытные Go-разработчики иногда спотыкаются на заголовках. Вот три самые частые проблемы:
Ошибка 1: Забыли про User-Agent
Некоторые API (например, GitHub) блокируют запросы без User-Agent. Всегда указывайте хотя бы "MyApp/1.0".
Ошибка 2: Неправильный формат заголовка
Заголовки чувствительны к регистру? Нет, но их значения — да. Bearer token с маленькой буквы может не сработать.
Ошибка 3: Использование http.Get с заголовками
Это невозможно. Если видите http.Get() в коде, где нужны заголовки — вы делаете что-то не так. Всегда используйте http.NewRequest().