Пустой ответ, 503, обрыв и повреждённый JSON
Содержание курса
Полученный ответ и ошибка отправки — разные наблюдения
Когда клиент вызывает client.Do(req), возможны принципиально разные исходы, и Go разграничивает их на уровне возвращаемых значений.
200 с пустым списком — сервер ответил нормально. Do вернул (resp, nil): ошибки нет, статус 200, тело декодируется без проблем, декодер создаёт корректный пустой срез []Article{}. Список длиной ноль — валидный результат, а не признак сбоя.
503 — сервер тоже ответил, HTTP-обмен завершился. Do снова вернул (resp, nil), ошибки Go нет. Но статус говорит, что операция недоступна. Информация о неудаче находится в resp.StatusCode, а не в значении err.
Обрыв соединения — HTTP-ответа не существует вообще. Do вернул (nil, err). Обращаться к resp в этой ветке нельзя: указатель не инициализирован.
200 с повреждённым JSON — HTTP-обмен прошёл успешно, Do вернул (resp, nil), статус 200. Сбой происходит позже, когда json.Decoder читает тело: он не может разобрать неполный или невалидный JSON и возвращает ошибку уже из Decode, а не из Do.
Из этой логики вытекает порядок проверок в коде:
| Наблюдение | Do вернул ошибку? |
Есть resp.StatusCode? |
Можно использовать список? |
|---|---|---|---|
200 и [] |
нет | да, 200 |
да, его длина равна нулю |
503 и тело ошибки |
нет | да, 503 |
нет |
| Обрыв соединения | да | нет | нет |
200 и повреждённый JSON |
нет | да, 200 |
нет |
Ниже функция, которая сохраняет это различие в печатном результате. Структура Article содержит поля ID string и Title string с JSON-тегами id и title, как в API статей. Для запуска полного файла нужен также код подменного транспорта из следующего шага.
func probe(ctx context.Context, client *http.Client) string {
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
"http://articles.test/articles", nil)
if err != nil {
return "invalid request: " + err.Error()
}
resp, err := client.Do(req)
if err != nil {
return "transport error: " + err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Sprintf("HTTP %d", resp.StatusCode)
}
var articles []Article
if err := json.NewDecoder(resp.Body).Decode(&articles); err != nil {
return "JSON error: " + err.Error()
}
return fmt.Sprintf("HTTP 200, articles=%d", len(articles))
}
Проверка err стоит до обращения к resp: при ошибке Do нельзя полагаться на обычный ответ с кодом. Do возвращает ответ 503 без Go-ошибки, поэтому статус проверяется отдельно второй проверкой. Тело каждого полученного ответа закрывается через defer — в том числе в ветке 503 и при ошибке разбора JSON: в обоих случаях resp.Body открыт и его нужно освободить. Для 200 [] декодер создаёт корректный пустой срез, а функция возвращает articles=0.
