Avatar ·

Как установить заголовки в HTTP GET запросе на Go?

Всем привет! Я новичок в Go и пытаюсь сделать HTTP GET запрос к внешнему API. Мне нужно передать несколько заголовков, например, `Authorization` и `Content-Type`. Я прочитал документацию, но никак не могу разобраться, как это правильно сделать. Вот мой текущий код, который не работает: ```go package main import ( "fmt" "io/ioutil" "net/http" ) func main() { url := "https://api.example.com/data" // Пытаюсь установить заголовки req, err := http.NewRequest("GET", url, nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer my-token") req.Header.Set("Content-Type", "application/json") // Выполняю запрос resp, err := http.Get(url) // Вот здесь проблема! if err != nil { panic(err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { panic(err) } fmt.Println(string(body)) } ``` Я создаю `http.Request` с заголовками, но потом использую `http.Get(url)`, который создаёт новый запрос без моих заголовков. Как правильно выполнить GET запрос с кастомными заголовками? Подскажите, пожалуйста, как это сделать правильно и почему мой подход не работает. Заранее спасибо! <<>>

Правильный способ: используйте клиент и метод Do()

Ваша проблема в том, что вы создаёте объект http.Request с заголовками, но затем вызываете http.Get(url), который создаёт новый запрос без ваших заголовков. В Go нужно использовать подготовленный запрос через клиент и метод Do().

Вот правильное решение:

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {
    url := "https://api.example.com/data"
    
    // 1. Создаём запрос
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        panic(err)
    }
    
    // 2. Устанавливаем заголовки
    req.Header.Set("Authorization", "Bearer my-token")
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Accept", "application/json") // дополнительный заголовок
    
    // 3. Создаём HTTP клиент (можно с таймаутом)
    client := &http.Client{}
    
    // 4. Выполняем запрос через клиент
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    
    // 5. Читаем ответ
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }
    
    fmt.Println("Статус:", resp.Status)
    fmt.Println("Тело ответа:", string(body))
}

Ключевые моменты:

  • Не используйте http.Get() — это функция-обёртка, которая создаёт запрос "с нуля" и не учитывает ваши заголовки.
  • Используйте client.Do(req) — этот метод выполняет подготовленный ранее запрос со всеми заголовками.
  • Создавайте клиент с таймаутом для production-кода: &http.Client{Timeout: 10 * time.Second}.
  • Заголовки чувствительны к регистру в Go — используйте канонические имена (например, "Content-Type", а не "content-type").

Расширенный пример с обработкой ошибок и таймаутом:

package main

import (
    "fmt"
    "io"
    "net/http"
    "time"
)

func main() {
    url := "https://api.example.com/data"
    
    // Создаём запрос
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        fmt.Printf("Ошибка создания запроса: %v\n", err)
        return
    }
    
    // Устанавливаем заголовки
    req.Header.Set("Authorization", "Bearer my-token")
    req.Header.Set("Content-Type", "application/json")
    
    // Клиент с таймаутом
    client := &http.Client{
        Timeout: 10 * time.Second,
    }
    
    // Выполняем запрос
    resp, err := client.Do(req)
    if err != nil {
        fmt.Printf("Ошибка выполнения запроса: %v\n", err)
        return
    }
    defer resp.Body.Close()
    
    // Читаем тело ответа
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Printf("Ошибка чтения ответа: %v\n", err)
        return
    }
    
    fmt.Printf("Статус: %d\n", resp.StatusCode)
    fmt.Printf("Тело: %s\n", body)
}

Почему ваш код не работал?

Функция http.Get(url) внутри делает следующее:

func Get(url string) (resp *Response, err error) {
    return DefaultClient.Get(url)
}

А DefaultClient.Get() создаёт новый запрос через http.NewRequest("GET", url, nil) и выполняет его. Все ваши заголовки остаются в первом, неиспользованном запросе.

Используйте client.Do(req) — это единственный правильный способ выполнить кастомный запрос в Go.

<<>>

Альтернативный подход: использование контекста и middleware

Если вам нужно добавлять одинаковые заголовки к множеству запросов (например, токен авторизации), можно создать функцию-helper или использовать middleware-паттерн.

Пример с функцией-обёрткой:

package main

import (
    "fmt"
    "io"
    "net/http"
    "time"
)

// Функция для создания запроса с заголовками
func newRequestWithHeaders(method, url string, headers map[string]string) (*http.Request, error) {
    req, err := http.NewRequest(method, url, nil)
    if err != nil {
        return nil, err
    }
    for key, value := range headers {
        req.Header.Set(key, value)
    }
    return req, nil
}

func main() {
    url := "https://api.example.com/data"
    
    headers := map[string]string{
        "Authorization": "Bearer my-token",
        "Content-Type":  "application/json",
    }
    
    req, err := newRequestWithHeaders("GET", url, headers)
    if err != nil {
        panic(err)
    }
    
    client := &http.Client{Timeout: 10 * time.Second}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    
    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))
}

Для более сложных случаев:

Используйте библиотеки вроде github.com/hashicorp/go-retryablehttp или создайте свой HTTP-клиент с переопределением метода Do(). Это удобно, когда нужно логировать запросы, добавлять заголовки или повторять неудачные попытки.

Главное правило: всегда явно создавайте http.Request и выполняйте его через client.Do(), а не используйте http.Get() для кастомных запросов.