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