От Go API к вызову другой программы

Пустой ответ, 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.

Начать заново?

Помощник начнёт новый диалог по этой задаче. Текущая переписка останется в истории до конца срока хранения.

О переписке

Можно приложить до трёх фото PNG, JPEG или WebP по 5 МБ.

Переписка и фото хранятся 90 дней после последнего принятого сообщения. Преподаватель получит доступ к диалогу, когда вы попросите его помочь.

Проверка решения остаётся отдельным действием.