Опишите процесс взаимодействия со сторонним API в Go

Ответ

Взаимодействие со сторонними API в Go строится на основе стандартного пакета net/http. Для более удобной работы можно использовать сторонние библиотеки, например, resty.

Базовый алгоритм:

  1. Создание HTTP-клиента: Используется http.Client. Рекомендуется настраивать таймаут, чтобы избежать зависания приложения.
  2. Формирование запроса: Создается объект http.Request с помощью http.NewRequest(). Здесь указывается метод (GET, POST и т.д.), URL и тело запроса (если необходимо).
  3. Добавление заголовков: Заголовки (например, Content-Type, Authorization) добавляются к запросу через req.Header.Set().
  4. Отправка запроса: Клиент выполняет запрос с помощью метода client.Do(req).
  5. Обработка ответа:
    • Проверяется статус-код ответа (resp.StatusCode).
    • Тело ответа (resp.Body) читается с помощью io.ReadAll.
    • Тело (чаще всего JSON) десериализуется в структуру Go с помощью json.Unmarshal.

Пример (GET-запрос с таймаутом и заголовком):

package main

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

// Структура для парсинга JSON-ответа
type MyStruct struct {
    UserID int    `json:"userId"`
    ID     int    `json:"id"`
    Title  string `json:"title"`
}

func main() {
    // 1. Создаем HTTP-клиент с таймаутом
    client := &http.Client{Timeout: 10 * time.Second}

    // 2. Формируем запрос
    req, err := http.NewRequest("GET", "https://jsonplaceholder.typicode.com/todos/1", nil)
    if err != nil {
        panic(err)
    }

    // 3. Добавляем заголовки
    req.Header.Add("Accept", "application/json")

    // 4. Отправляем запрос
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    // 5. Обрабатываем ответ
    if resp.StatusCode != http.StatusOK {
        fmt.Println("Error: status code", resp.StatusCode)
        return
    }

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }

    var result MyStruct
    if err := json.Unmarshal(body, &result); err != nil {
        panic(err)
    }

    fmt.Printf("Результат: %+vn", result)
}

Продвинутые практики (Production-ready):

  • Context: Использование context.WithTimeout или context.WithCancel для контроля над жизненным циклом запроса.
  • Retry-логика: Реализация повторных запросов при временных сбоях сети или сервера (например, с экспоненциальной задержкой).
  • Circuit Breaker: Использование паттерна "Предохранитель" (например, с библиотекой sony/gobreaker) для предотвращения каскадных сбоев.
  • Логирование: Подробное логирование запросов, ответов и ошибок.
  • Метрики: Сбор метрик (время ответа, количество ошибок) для мониторинга.

Ответ 18+ 🔞

А, ну ты смотри, решил пообщаться с внешним миром из своего Go-приложения, да? APIшки всякие дергать, как какой-нибудь интернет-маньяк. Ёпта, ну что ж, расскажу, как это без косяков делается, а то насмотришься кривых примеров и потом будешь как Герасим — «Му-му» говорить, а нихуя не работать будет.

В общем, вся эта движуха крутится вокруг пакета net/http. Это наш родной, отечественный инструмент, типа лома. Можно, конечно, взять какую-нибудь обёртку, вроде resty, чтобы жизнь малиной не казалась, но давай начнём с азов, а то мозги не вынесут.

Базовый план, как не облажаться:

  1. Клиента создаём. http.Client — наш боевой конь. Главное — не забудь ему таймаут впихнуть, а то твоё приложение зависнет, как будто оно в депрессии, и будет ждать ответа до второго пришествия. Timeout: 10 * time.Second — святое.
  2. Запрос лепим. Берём http.NewRequest(). Тыкаем туда метод (GET, POST, да хоть DELETE), URL-адрес (куда стучимся) и тело, если надо (для POST, например).
  3. Шапку надеваем. Заголовки — это как визитка. Без Content-Type: application/json тебя на пороге большинства API просто пошлют нахуй. Добавляются через req.Header.Set().
  4. В бой отправляем. Команда client.Do(req) — это наш выстрел. Жмём на курок.
  5. Ответ разбираем. Тут самое интересное, а для многих — пиздец.
    • Сначала смотрим статус-код (resp.StatusCode). Если не 200 OK, а что-то вроде 404 или 500 — значит, уже где-то накосячили, и пора грустить.
    • Читаем тело ответа (resp.Body) через io.ReadAll. Не забудь потом это тело закрыть (defer resp.Body.Close()), а то утечка памяти будет — хуже, чем в хрущёвке.
    • Если ответ в JSON (а он почти всегда в JSON), то парсим эту хуйню в свою родную Go-структуру с помощью json.Unmarshal. Заранее структуру опиши, с тегами, а то опять «Му-му» получится.

Вот тебе живой пример, как GET-запрос с таймаутом и шапкой сделать:

package main

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

// Структура, в которую будем пихать ответ. Теги — это наше всё.
type MyStruct struct {
    UserID int    `json:"userId"`
    ID     int    `json:"id"`
    Title  string `json:"title"`
}

func main() {
    // 1. Клиент с таймаутом. 10 секунд — и хватит.
    client := &http.Client{Timeout: 10 * time.Second}

    // 2. Лепим запрос. GET, вот этот URL, тела нет.
    req, err := http.NewRequest("GET", "https://jsonplaceholder.typicode.com/todos/1", nil)
    if err != nil {
        panic(err) // В продакшене, ясное дело, так не делают. Но ты пока запомни.
    }

    // 3. Надеваем шапку. Говорим: "Друг, я жду от тебя JSON!"
    req.Header.Add("Accept", "application/json")

    // 4. Стреляем.
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close() // Закрыть, блядь, не забудь! Это важно!

    // 5. Разбираем, что нам прислали.
    if resp.StatusCode != http.StatusOK {
        fmt.Println("Ой, всё! Код ошибки:", resp.StatusCode)
        return
    }

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }

    var result MyStruct
    if err := json.Unmarshal(body, &result); err != nil {
        panic(err)
    }

    fmt.Printf("Вот что получилось: %+vn", result)
}

А теперь, для продвинутых юзеров (чтобы в проде не вышло волнение ебать):

  • Context, ёпта! Используй context.WithTimeout. Это чтобы можно было запрос по-человечески отменить, если он затупил, а не ждать, пока таймаут клиента сработает.
  • Повторные попытки (Retry). Сеть — она такая сука, может глюкнуть. Надо уметь послать запрос ещё разок-другой, если первая попытка обосралась. Желательно с экспоненциальной задержкой, чтобы не заDDOSить сервер.
  • Предохранитель (Circuit Breaker). Если удалённый API лег и не встаёт, не надо долбить его запросами каждую секунду. Включи «предохранитель» (библиотека sony/gobreaker), дай ему передохнуть. Паттерн, блядь, важный!
  • Логируй всё. Что отправил, что получил, сколько времени заняло. Потом, когда всё ебнется, будешь знать, где искать.
  • Метрики. Замеряй время ответа, считай ошибки. Чтобы потом с умным видом на графики смотреть и говорить: «Ага, вот тут у нас сервис начал тормозить, как последняя мартышлюшка».

Вот, вроде всё. Главное — не тупи, закрывай Body и таймауты ставь. А то будет тебе трагедия, как у того Герасима, только с памятью и горой запросов.