Отмена HTTP-запроса в Go: как убедиться, что context дошёл до клиента
Клиент закрыл запрос, но ваш Go-сервис продолжает обращаться к зависимому API. Созданный context не помогает, если фактический сетевой вызов не использует его. Проверять нужно передачу отмены по всей цепочке, а не присутствие переменной ctx.
Как разобрать ситуацию
Передавайте контекст входящего запроса в исходящий через NewRequestWithContext. Для отдельного лимита операции создайте дочерний context с deadline и вызовите cancel после завершения. Общий бюджет времени включает несколько этапов; если каждой зависимости выдать полный исходный лимит независимо, весь запрос способен занять дольше ожидаемого.
Пример и ожидаемый результат
Минимальная программа отменяет context до сетевого вызова и проверяет errors.Is(err, context.Canceled). Здесь результат не зависит от скорости сети: отмена уже произошла. Такой тест проверяет подключение механизма к HTTP-запросу, а не производительность удалённого сервера.
package main
import (
"context"
"errors"
"fmt"
"net/http"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", "http://127.0.0.1:1", nil)
_, err := http.DefaultClient.Do(req)
fmt.Println(errors.Is(err, context.Canceled))
}
Ожидаемый вывод программы — true. Это подтверждает, что стандартный клиент вернул ошибку, связанную с отменой запроса.
Проверка перед применением
- Проверьте отмену на уровне обработчика, исходящего HTTP-вызова и обращения к базе.
- Не заменяйте входящий context на Background без явной причины: это обрывает связь отмены.
- Отдельно определите, какие фоновые действия должны завершиться даже после ухода клиента.
Для расследования зависаний логируйте длительность этапов и причину завершения, не только общий код 500. Тогда можно отличить отмену пользователем от превышения вашего deadline и отказа зависимости. Переименование всех этих случаев в timeout мешает выбрать корректное исправление и завышает число якобы нестабильных внешних сервисов.
Границы решения
Пример не доказывает, что удалённая система прекратила уже начатую операцию. Отмена ожидания на клиенте и откат внешнего побочного эффекта — разные вещи. Проверка выполнена локально стандартной библиотекой Go.
Если базовый Go уже знаком и хочется связать HTTP, хранение данных и обработку отказов в одну систему, посмотрите наш платный курс
Микросервисы на Go: HTTP, PostgreSQL, gRPC, Kafka и Docker. Перед записью сравните программу со своей задачей и подготовкой.
Техническая справка: go.dev: документация, pkg.go.dev: документация.
Следующий шаг: Go backend: HTTP, конкурентность и устойчивость сервиса. По смежным вопросам: Плюс и амперсанд потерялись в URL Go-клиента: как собрать query, HTTP retry в Go: почему один список кодов не делает повтор безопасным.
Продолжить на практике
- Микросервисы на Go: HTTP, PostgreSQL, gRPC, Kafka и Docker ↗
От работающего Go API — к взаимодействию сервисов, которому можно доверять. Разберитесь, как проектировать контракты, сохранять согласованность данных и восстанавливать обработку после сбоев. HTTP, PostgreSQL, gRPC, Kafka и Docker — через код и наглядные разборы.
