HTTP-запросы в Go: как отправлять GET, POST, PUT и DELETE

Что в двух словах означает HTTP-запрос
HTTP — протокол приложения для обмена запросами и ответами между клиентом (браузером или программой) и сервером. Запрос содержит метод (GET, POST и т.д.), URL ресурса, заголовки и опциональное тело. Методы отражают CRUD: GET — чтение, POST — создание, PUT — обновление, DELETE — удаление.
Определение термина: HTTP-запрос — структурированное сообщение, которое клиент отправляет серверу с намерением получить или изменить ресурс.
Важно: PUT и PATCH часто используются для обновления; PUT обычно ожидает полное представление ресурса, PATCH — частичное.
Почему именно Go и пакет net/http
Пакет net/http — стандартная библиотека Go для HTTP. Он предоставляет всё необходимое: создание серверов, формирование запросов, управление клиентом и обработку ответов. Большинство web-фреймворков на Go строятся поверх этого пакета.
Преимущества:
- Стандартная реализация, поддерживаемая сообществом.
- Простая интеграция с контекстом (context.Context) и таймаутами.
- Высокая производительность и хорошая читаемость кода.
Замечание: для упрощённых вызовов можно использовать http.Get и http.Post, но для контроля заголовков, таймаутов и тела предпочтительнее http.NewRequest + http.Client.
Шаблон: как формируется HTTP-запрос в Go
- Создаём URL и тело запроса (если нужно).
- Формируем request := http.NewRequest(method, url, body).
- Устанавливаем заголовки request.Header.Set(…).
- Создаём клиента client := &http.Client{Timeout: …} при необходимости.
- Отправляем response, err := client.Do(request).
- Читаем response.Body через io.ReadAll и закрываем body (defer response.Body.Close()).
- Обрабатываем статус и тело.
Совет: всегда задавайте таймауты в http.Client, чтобы избежать зависших соединений.
POST: пример создания ресурса
Ниже функция createUser отправляет POST на https://reqres.in/api/users для создания пользователя. Код сохранён из примера и показывает базовый цикл: построение запроса, отправка, чтение ответа и форматирование JSON.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func createUser(name, job string) {
fmt.Println("Creating user...")
apiUrl := "https://reqres.in/api/users"
userData := []byte(`{"name":"` + name + `","job":"` + job + `"}`)
// create new http request
request, error := http.NewRequest("POST", apiUrl, bytes.NewBuffer(userData))
request.Header.Set("Content-Type", "application/json; charset=utf-8")
// send the request
client := &http.Client{}
response, error := client.Do(request)
if error != nil {
fmt.Println(error)
}
responseBody, error := io.ReadAll(response.Body)
if error != nil {
fmt.Println(error)
}
formattedData := formatJSON(responseBody)
fmt.Println("Status: ", response.Status)
fmt.Println("Response body: ", formattedData)
// clean up memory after execution
defer response.Body.Close()
}
Функция formatJSON форматирует байтовый массив JSON для удобного чтения:
// function to format JSON data
func formatJSON(data []byte) string {
var out bytes.Buffer
err := json.Indent(&out, data, "", " ")
if err != nil {
fmt.Println(err)
}
d := out.Bytes()
return string(d)
}
Вызывается так:
func main() {
fmt.Println("Making POST request...")
createUser("Tim Omolana", "Writer")
}
Вывод в терминале (пример):

Важно: всегда закрывайте response.Body вызовом defer response.Body.Close() сразу после проверки ошибки, чтобы избежать утечек соединений.
GET: получение ресурса
GET-запрос не содержит тела. Пример функции getUser, которая получает пользователя по id:
// main.go
func getUser(id string) {
fmt.Println("Getting user by ID...")
// make GET request to API to get user by ID
apiUrl := "https://reqres.in/api/users/" + id
request, error := http.NewRequest("GET", apiUrl, nil)
if error != nil {
fmt.Println(error)
}
request.Header.Set("Content-Type", "application/json; charset=utf-8")
client := &http.Client{}
response, error := client.Do(request)
if error != nil {
fmt.Println(error)
}
responseBody, error := io.ReadAll(response.Body)
if error != nil {
fmt.Println(error)
}
formattedData := formatJSON(responseBody)
fmt.Println("Status: ", response.Status)
fmt.Println("Response body: ", formattedData)
// clean up memory after execution
defer response.Body.Close()
}
Пример вызова:
func main() {
fmt.Println("Making GET request...")
getUser("2")
}

Замечание: для запросов с параметрами используйте net/url для безопасного кодирования query-параметров.
PUT: обновление ресурса
PUT похож на POST, но обычно обновляет существующий ресурс и требует указания идентификатора в URL.
// main.go
func updateUser(name, job, id string) {
fmt.Println("Updating user...")
// make PUT request to API to update user
apiUrl := "https://reqres.in/api/users/" + id
userData := []byte(`{"name":"` + name + `","job":"` + job + `"}`)
// create new http PUT request
request, error := http.NewRequest("PUT", apiUrl, bytes.NewBuffer(userData))
request.Header.Set("Content-Type", "application/json; charset=utf-8")
// Remaining function body from createUser function...
// Make request, get response, and clear memory...
}
Пример вызова:
func main() {
// update entry with the ID 2.
updateUser("Tim Newname", "Staff Writer", "2")
}

Совет: при частичных обновлениях используйте PATCH (если API поддерживает). PUT чаще подразумевает замену всего ресурса.
DELETE: удаление ресурса
DELETE удаляет ресурс по URI. Ответ часто содержит только статус (без тела).
func deleteUser(id string) {
fmt.Println("Deleting user...")
// make DELETE request to API to delete user
apiUrl := "https://reqres.in/api/users/" + id
// create new http request
request, error := http.NewRequest("DELETE", apiUrl, nil)
request.Header.Set("Content-Type", "application/json; charset=utf-8")
client := &http.Client{}
response, error := client.Do(request)
if error != nil {
fmt.Println(error)
}
fmt.Println("Status: ", response.Status)
}
Вызов:
func main() {
fmt.Println("Making DELETE request...")
deleteUser("2")
}

Важно: некоторые API на DELETE возвращают тело с подтверждением, другие — только статус 204 No Content.
Упрощённые методы: http.Get и http.Post
Для быстрых запросов можно использовать:
- http.Get(url) — выполняет GET и возвращает response, err
- http.Post(url, contentType, body) — выполняет POST
Они удобны, но дают меньше контроля (заголовки, таймауты, кастомные клиентские настройки). Для продакшена предпочтительнее http.NewRequest с настраиваемым http.Client.
Шаблон безопасности и обработки ошибок
Important: всегда проверяйте ошибки после каждой сетевой операции и закрывайте response.Body.
Рекомендации по безопасности:
- Используйте HTTPS везде, где это возможно.
- Валидируйте и предотвращайте внедрение данных в JSON/URL.
- Обрабатывайте таймауты и отмену через context.
- Ограничивайте повторные попытки и контролируйте backoff.
GDPR/Конфиденциальность: не логируйте PII (персональные данные) в открытом виде. Маскируйте чувствительные поля перед записью в логи.
Отладка и тестирование
Чек-лист для отладки:
- Проверить URL и query-параметры (используйте net/url).
- Убедиться, что Content-Type соответствует телу запроса.
- Посмотреть raw-трафик через прокси (mitmproxy) или tcpdump при отладке.
- Тестировать ответы с mock-серверами или тестовыми эндпоинтами, такими как reqres.in.
Критерии приёмки:
- Запрос успешно отправляется без ошибок.
- Ответ имеет ожидаемый HTTP-статус (200/201/204 и т.д.).
- Тело ответа корректно декодируется в структуру (если предусмотрено).
- Время отклика укладывается в требуемый SLO.
Быстрая шпаргалка (cheat sheet)
- Создать request: req, err := http.NewRequest(“METHOD”, url, body)
- Установить заголовок: req.Header.Set(“Content-Type”, “application/json”)
- Запустить: client := &http.Client{Timeout: 10 * time.Second}; resp, err := client.Do(req)
- Прочитать тело: body, err := io.ReadAll(resp.Body); defer resp.Body.Close()
- Упростить GET: resp, err := http.Get(url)
- Упростить POST JSON: resp, err := http.Post(url, “application/json”, bytes.NewBuffer(jsonBytes))
Сравнение HTTP-методов (краткая матрица)
| Метод | Назначение | Тело запроса | Идемпотентность |
|---|---|---|---|
| GET | Чтение | Нет | Да |
| POST | Создание | Да | Нет |
| PUT | Замена/обновление | Да | Да |
| PATCH | Частичное обновление | Да | Частично |
| DELETE | Удаление | Обычно нет | Да |
Примечание: идемпотентность — свойство безопасного повторного выполнения без изменения состояния несколько раз.
Ментальные модели и эвристики
- “CRUD → HTTP”: связывайте бизнес-операцию (создать/читать/обновить/удалить) с соответствующим HTTP-методом.
- “Тело только когда нужно”: не отправляйте тело в GET/DELETE по умолчанию; используйте query-параметры.
- “Короткие таймауты в клиенте”: таймауты защищают от зависших сетевых вызовов.
- “Опирайтесь на статус”: всегда проверяйте HTTP-статус перед декодированием тела.
Ролевые чек-листы
Разработчик:
- Добавить обработку ошибок и таймауты.
- Использовать context для отмены.
- Покрыть функцию unit-тестами с моками.
QA-инженер:
- Смоделировать неуспешные статусы (500, 429, 401).
- Проверить поведение при обрыве соединения.
- Валидировать обработку некорректного JSON.
Ops/DevOps:
- Настроить мониторинг ошибок и латентности.
- Ограничить скорость запросов к внешним API.
- Настроить секреты/ключи через безопасный сторедж.
Decision flow: как выбрать метод (Mermaid)
flowchart TD
A[Нужно ли изменить ресурс?] -->|Нет| B[GET]
A -->|Да| C[Создать или обновить?]
C -->|Создать| D[POST]
C -->|Обновить| E[Полное обновление -> PUT]
C -->|Частичное| F[PATCH]
G[Удалить?] -->|Да| H[DELETE]
style A fill:#f9f,stroke:#333,stroke-width:1pxПримеры ошибок и когда подход не сработает
- Если API требует аутентификацию через нестандартные заголовки — используйте request.Header и добавьте токен.
- Если требуется потоковая обработка (streaming), простое чтение io.ReadAll не подойдёт — используйте потоковое чтение и декодирование.
- Для больших файлов используйте multipart/form-data и io.Pipe вместо загрузки всего в память.
Советы по производительности
- Переиспользуйте http.Client для множества запросов (не создавайте новый клиент на каждый запрос).
- Используйте Keep-Alive и контролируйте MaxIdleConns в transport, если много одновременных запросов.
- При необходимости включите gzip-сжатие на стороне сервера и указывайте Accept-Encoding в заголовках.
Короткий глоссарий (одно предложение каждый термин)
- HTTP: протокол для передачи гипертекста и запросов в сети.
- REST API: архитектурный стиль для взаимодействия через HTTP.
- idempotent: операция, повторённая несколько раз, даёт тот же результат.
- JSON: формат обмена данными, часто используемый в REST API.
Итог и рекомендации
Net/http в Go — универсальный инструмент для работы с HTTP. Для небольших скриптов подойдёт http.Get/http.Post, для производственных приложений — http.NewRequest с настраиваемым http.Client, таймаутами и обработкой ошибок. Тестируйте через mock-серверы, логируйте аккуратно (без PII) и используйте HTTPS.
Summary:
- Используйте http.NewRequest + http.Client для гибкости.
- Всегда обрабатывайте ошибки и закрывайте response.Body.
- Настраивайте таймауты и контекст для отмены.
Если хотите, могу добавить пример с аутентификацией Bearer Token, демо с context.WithTimeout или тесты с httptest.Server.
Похожие материалы
Macro Focus на Pixel 7 Pro — макро фото и видео
Как изменить циферблат Apple Watch и настроить компликации
Обновление драйверов NVIDIA — быстро и безопасно
Drummer в GarageBand — полный практический гид
Копирование, перемещение и удаление файлов в PowerShell