resty для Go: полное руководство с примерами

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

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

Начать курс

resty для Go: мощный и элегантный HTTP-клиент

В мире Go существует множество библиотек для работы с HTTP-запросами. Стандартный пакет net/http предоставляет базовый функционал, но для реальных проектов часто требуется нечто более удобное и функциональное. Библиотека resty — это именно то, что нужно разработчикам, которые ценят простоту, читаемость кода и мощные возможности "из коробки".

Resty позиционируется как "Go-клиент для REST API, вдохновленный Ruby-библиотекой RestClient". Она предоставляет fluent-интерфейс (цепочки вызовов), автоматическую обработку JSON, встроенные механизмы повторных попыток, таймауты, middleware и многое другое. Благодаря своей архитектуре, resty позволяет писать лаконичный и выразительный код для взаимодействия с любыми HTTP-сервисами — от простых GET-запросов до сложных многоэтапных API-интеграций.

Зачем использовать resty вместо стандартного net/http? Во-первых, она значительно сокращает boilerplate-код. Вам не нужно вручную создавать структуры запросов, устанавливать заголовки, парсить JSON-ответы. Во-вторых, resty предоставляет удобные абстракции для работы с куками, аутентификацией, прокси и файловыми загрузками. В-третьих, библиотека активно поддерживается, имеет отличную документацию и используется в тысячах production-проектов. Это делает resty одним из лучших выборов для HTTP-клиента в экосистеме Go.

Установка

Установка resty выполняется стандартным для Go способом — через менеджер зависимостей go get:

go get -u github.com/go-resty/resty/v2

После выполнения этой команды библиотека будет добавлена в ваш go.mod файл. Убедитесь, что вы используете Go версии 1.16 или выше для поддержки модулей.

В вашем коде импорт будет выглядеть так:

import "github.com/go-resty/resty/v2"

Быстрый старт

Давайте начнем с минимального рабочего примера — выполним GET-запрос к публичному API и выведем результат:

package main

import (
    "fmt"
    "github.com/go-resty/resty/v2"
)

func main() {
    // Создаем клиент
    client := resty.New()

    // Выполняем GET-запрос
    resp, err := client.R().
        SetHeader("Accept", "application/json").
        Get("https://jsonplaceholder.typicode.com/posts/1")

    if err != nil {
        fmt.Println("Ошибка:", err)
        return
    }

    fmt.Println("Статус:", resp.StatusCode())
    fmt.Println("Тело ответа:", resp.String())
}

Этот пример демонстрирует ключевую особенность resty — fluent-интерфейс. Мы создаем клиент, затем через метод R() создаем новый запрос, устанавливаем заголовок и выполняем GET. Все вызовы объединяются в цепочку, что делает код очень читаемым.

Основные методы и функции

Рассмотрим 10 ключевых методов resty, которые покрывают 90% всех задач при работе с HTTP.

1. resty.New()

Сигнатура: func New() *Client

Что делает: Создает новый экземпляр HTTP-клиента с настройками по умолчанию. Это отправная точка для всех операций.

client := resty.New()
// Теперь client готов к использованию

2. Client.R()

Сигнатура: func (c *Client) R() *Request

Что делает: Создает новый объект запроса. Все методы для настройки запроса (заголовки, тело, параметры) вызываются на этом объекте.

request := client.R()
request.SetHeader("Content-Type", "application/json")
// ... дальнейшая работа с запросом

3. Request.Get(url)

Сигнатура: func (r *Request) Get(url string) (*Response, error)

Что делает: Выполняет HTTP GET-запрос к указанному URL. Возвращает ответ и ошибку.

resp, err := client.R().Get("https://api.example.com/users")
if err != nil {
    log.Fatal(err)
}
fmt.Println(resp.String())

4. Request.Post(url)

Сигнатура: func (r *Request) Post(url string) (*Response, error)

Что делает: Выполняет HTTP POST-запрос. Обычно используется для отправки данных на сервер.

resp, err := client.R().
    SetBody(map[string]interface{}{"name": "John", "age": 30}).
    Post("https://api.example.com/users")
if err != nil {
    log.Fatal(err)
}
fmt.Println(resp.String())

5. Request.SetBody(body)

Сигнатура: func (r *Request) SetBody(body interface{}) *Request

Что делает: Устанавливает тело запроса. Если передать структуру или map, resty автоматически сериализует её в JSON (по умолчанию) или XML (если указан соответствующий заголовок).

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

user := User{Name: "Alice", Age: 25}
resp, err := client.R().
    SetBody(user).
    Post("https://api.example.com/users")

6. Request.SetResult(result)

Сигнатура: func (r *Request) SetResult(result interface{}) *Request

Что делает: Указывает, в какую структуру нужно десериализовать успешный ответ (коды 2xx). Это избавляет от необходимости вручную парсить JSON.

var users []User
resp, err := client.R().
    SetResult(&users).
    Get("https://api.example.com/users")
if err != nil {
    log.Fatal(err)
}
// users уже заполнен данными из ответа
fmt.Printf("Получено %d пользователей\n", len(users))

7. Request.SetError(error)

Сигнатура: func (r *Request) SetError(err interface{}) *Request

Что делает: Аналогично SetResult, но для обработки ошибок (коды 4xx и 5xx). Позволяет десериализовать тело ответа с ошибкой в структуру.

type APIError struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
}

var apiErr APIError
resp, err := client.R().
    SetError(&apiErr).
    Get("https://api.example.com/nonexistent")
if err != nil {
    log.Fatal(err)
}
if resp.IsError() {
    fmt.Printf("API ошибка: %s\n", apiErr.Message)
}

8. Request.SetQueryParams(params)

Сигнатура: func (r *Request) SetQueryParams(params map[string]string) *Request

Что делает: Устанавливает параметры запроса (query string). Удобно для фильтрации, пагинации и других GET-параметров.

resp, err := client.R().
    SetQueryParams(map[string]string{
        "page":     "2",
        "limit":    "10",
        "sort":     "name",
    }).
    Get("https://api.example.com/users")
// Реальный URL: https://api.example.com/users?page=2&limit=10&sort=name

9. Client.SetRetryCount(count)

Сигнатура: func (c *Client) SetRetryCount(count int) *Client

Что делает: Устанавливает количество повторных попыток при неудачном запросе. Полезно для обработки временных сетевых ошибок.

client := resty.New()
client.SetRetryCount(3).
    SetRetryWaitTime(1 * time.Second).
    SetRetryMaxWaitTime(5 * time.Second)

// Все запросы от этого клиента будут автоматически повторяться до 3 раз

10. Request.SetAuthToken(token)

Сигнатура: func (r *Request) SetAuthToken(token string) *Request

Что делает: Устанавливает Bearer-токен для аутентификации. Автоматически добавляет заголовок Authorization: Bearer <token>.

resp, err := client.R().
    SetAuthToken("my-secret-token-123").
    Get("https://api.example.com/protected")
// Заголовок: Authorization: Bearer my-secret-token-123

Реальный практический пример

Давайте создадим полноценный клиент для работы с GitHub API. Этот пример демонстрирует типичный сценарий использования resty в production-проекте:

package main

import (
    "fmt"
    "log"
    "time"
    "github.com/go-resty/resty/v2"
)

// Структуры для работы с GitHub API
type GitHubUser struct {
    Login     string `json:"login"`
    ID        int    `json:"id"`
    Name      string `json:"name"`
    PublicRepos int  `json:"public_repos"`
    Followers int    `json:"followers"`
    CreatedAt string `json:"created_at"`
}

type GitHubRepo struct {
    Name        string `json:"name"`
    Description string `json:"description"`
    Stars       int    `json:"stargazers_count"`
    Forks       int    `json:"forks_count"`
    Language    string `json:"language"`
    UpdatedAt   string `json:"updated_at"`
}

type GitHubError struct {
    Message string `json:"message"`
}

// GitHubClient - наш клиент для работы с GitHub API
type GitHubClient struct {
    client *resty.Client
    token  string
}

// NewGitHubClient создает новый клиент с настройками
func NewGitHubClient(token string) *GitHubClient {
    client := resty.New().
        SetBaseURL("https://api.github.com").
        SetTimeout(10 * time.Second).
        SetRetryCount(3).
        SetRetryWaitTime(1 * time.Second).
        SetRetryMaxWaitTime(5 * time.Second).
        SetHeader("Accept", "application/vnd.github.v3+json").
        SetHeader("User-Agent", "MyAwesomeApp/1.0")

    if token != "" {
        client.SetAuthToken(token)
    }

    return &GitHubClient{
        client: client,
        token:  token,
    }
}

// GetUser получает информацию о пользователе GitHub
func (g *GitHubClient) GetUser(username string) (*GitHubUser, error) {
    var user GitHubUser
    var apiErr GitHubError

    resp, err := g.client.R().
        SetResult(&user).
        SetError(&apiErr).
        Get(fmt.Sprintf("/users/%s", username))

    if err != nil {
        return nil, fmt.Errorf("network error: %w", err)
    }

    if resp.IsError() {
        return nil, fmt.Errorf("API error (status %d): %s", 
            resp.StatusCode(), apiErr.Message)
    }

    return &user, nil
}

// GetUserRepos получает список репозиториев пользователя
func (g *GitHubClient) GetUserRepos(username string, page, perPage int) ([]GitHubRepo, error) {
    var repos []GitHubRepo
    var apiErr GitHubError

    resp, err := g.client.R().
        SetResult(&repos).
        SetError(&apiErr).
        SetQueryParams(map[string]string{
            "page":     fmt.Sprintf("%d", page),
            "per_page": fmt.Sprintf("%d", perPage),
            "sort":     "updated",
            "type":     "owner",
        }).
        Get(fmt.Sprintf("/users/%s/repos", username))

    if err != nil {
        return nil, fmt.Errorf("network error: %w", err)
    }

    if resp.IsError() {
        return nil, fmt.Errorf("API error (status %d): %s", 
            resp.StatusCode(), apiErr.Message)
    }

    return repos, nil
}

// CreateRepo создает новый репозиторий (требуется токен)
func (g *GitHubClient) CreateRepo(name, description string, isPrivate bool) (*GitHubRepo, error) {
    if g.token == "" {
        return nil, fmt.Errorf("authentication required to create repos")
    }

    body := map[string]interface{}{
        "name":        name,
        "description": description,
        "private":     isPrivate,
        "auto_init":   true,
    }

    var repo GitHubRepo
    var apiErr GitHubError

    resp, err := g.client.R().
        SetBody(body).
        SetResult(&repo).
        SetError(&apiErr).
        Post("/user/repos")

    if err != nil {
        return nil, fmt.Errorf("network error: %w", err)
    }

    if resp.IsError() {
        return nil, fmt.Errorf("API error (status %d): %s", 
            resp.StatusCode(), apiErr.Message)
    }

    return &repo, nil
}

// GetRateLimit получает информацию о лимитах API
func (g *GitHubClient) GetRateLimit() (map[string]interface{}, error) {
    var result map[string]interface{}
    
    resp, err := g.client.R().
        SetResult(&result).
        Get("/rate_limit")

    if err != nil {
        return nil, fmt.Errorf("network error: %w", err)
    }

    if resp.IsError() {
        return nil, fmt.Errorf("API error: status %d", resp.StatusCode())
    }

    return result, nil
}

func main() {
    // Создаем клиент (токен можно получить на GitHub Settings > Developer settings)
    client := NewGitHubClient("") // Без токена — только публичные данные

    // 1. Получаем информацию о пользователе
    fmt.Println("=== Информация о пользователе ===")
    user, err := client.GetUser("golang")
    if err != nil {
        log.Printf("Ошибка получения пользователя: %v\n", err)
    } else {
        fmt.Printf("Логин: %s\n", user.Login)
        fmt.Printf("Имя: %s\n", user.Name)
        fmt.Printf("Публичных репозиториев: %d\n", user.PublicRepos)
        fmt.Printf("Подписчиков: %d\n", user.Followers)
    }

    // 2. Получаем список репозиториев
    fmt.Println("\n=== Репозитории пользователя ===")
    repos, err := client.GetUserRepos("golang", 1, 5)
    if err != nil                    

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