Отмена 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 — через код и наглядные разборы.