Как задать заголовки в HTTP GET запросе на Go: полный гайд

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как задать заголовки в 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().

Продвинутый пример: Работа с cookies и редиректами

Блоги

Рекомендаций по книгам