Http-клиент: более быстрый тайм-аут при отсутствии сети

При выполнении HTTP-запроса Get в Go он ожидает полного тайм-аута, прежде чем вернуть ошибку, даже если нет сетевого подключения.

Я предполагаю, что внутри он довольно быстро понимает, что вышел из строя; Я бы хотел, чтобы эта ошибка распространялась как можно скорее, а не ждала тайм-аута. Я хочу, чтобы он пробовал 20 секунд, когда сеть есть и просто медленная. Как я могу настроить клиента с таким поведением?

Код для просмотра проблемы:

var client = &http.Client{
    Timeout: time.Second * 20,
}

response, err := client.Get(url)

Если это имеет значение, я использую gomobile, и он работает на симуляторе iOS.

«Я предполагаю, что внутри он довольно быстро знает, что произошел сбой», нет, это не так, поэтому он должен пройти по тайм-ауту.

JimB 20.05.2023 20:11

Отвечает ли это на ваш вопрос? Проверить подключение к интернету из приложения

Oshawott 20.05.2023 20:32

@JimB, можете ли вы добавить некоторые подробности, объясняющие это? Первоначальное рукопожатие TCP завершится ошибкой без подключенного сетевого интерфейса, и в этот момент шансов на успех нет. Что он делает следующие 19,99 секунды?

scosman 21.05.2023 04:30

@Oshawott, этот ответ предлагает сделать тот же вызов Get, который мы делаем здесь. У него будет такая же проблема (длительный тайм-аут).

scosman 21.05.2023 04:31

Если вы отправляете что-то по сети, единственный способ узнать, удалось ли это, — это дождаться ответа. Этот ответ может прийти в любое время или никогда, как долго вы будете ждать, зависит от вас, но ожидание некоторого времени — это единственное, что вы можете сделать.

JimB 21.05.2023 15:48

Это не тот случай, когда вы отправляете что-то и не знаете, сколько времени займет что-то внешнее. Сетевой стек сначала выбирает активный сетевой интерфейс (например, eth0, wlan1) для отправки. Этот вопрос касается случая, когда нет активного сетевого интерфейса, ничего не отправляется, и он довольно быстро выходит из строя (полностью локальный для этого устройства). В этом случае ему не нужно ждать ответа (ничего не было отправлено), и он должен иметь возможность быстро ошибаться. Это может быть абстрагировано, и нет простого пути, но это возможно, и сетевые стеки на других языках делают это.

scosman 21.05.2023 16:54

Если нет активного интерфейса, то вызов сразу же завершится ошибкой, потому что вы не сможете ничего привязать или отправить. Я только что дважды проверил, и отключение интерфейса делает именно это с HTTP-транспортом по умолчанию. Если локальная сеть не работает и возвращает подходящую ошибку icmp от DNS, она выходит из строя через пару миллисекунд. Если вы ждете полного таймаута, это потому, что ОС предоставила вам рабочий сетевой интерфейс, и вы отправляете пакеты в пустоту.

JimB 21.05.2023 17:00

Я все еще вижу 20-е на платформе, которую я упомянул (симулятор iOS, перейти на мобильный / gobind). Похоже, это может быть проблема с конкретной платформой. Тем не менее, я думаю, что вопрос действителен, и приведенные выше комментарии нацелены на случай, отличный от описанного в вопросе. Я попробую реальное устройство позже - может быть проблема с привязкой к сети симулятора, и ответ таков: симулятор iOS не идеален.

scosman 21.05.2023 17:10

Вы начинаете с предположения, что среда выполнения знает, что вызов не удался, и просто ждет полных 20 секунд, чтобы уведомить вас, что было бы довольно нелепо. Вы можете либо самостоятельно проверить сеть с коротким тайм-аутом (как это делают некоторые современные браузеры), либо уменьшить часть общего тайм-аута. В большинстве случаев вы можете безопасно установить время ожидания подключения на что-то гораздо более короткое, сохраняя при этом общее время ожидания прежним.

JimB 21.05.2023 17:14

Что касается симулятора, он, вероятно, работает просто отлично. Это наиболее распространенное состояние сбоя сети, вы столкнетесь со сломанными сетями гораздо чаще, чем с полным отключением сети устройства. Вам также необходимо учитывать, что вы можете повторно использовать существующее соединение, и в этом случае у среды выполнения нет другого выбора, кроме как предположить, что оно работает, и ждать тайм-аута приема или запроса.

JimB 21.05.2023 17:27

Я не делал такого предположения. Я наблюдал неидеальный результат, которого технически можно избежать, который вполне реален и постоянно воспроизводится. Затем я спросил переполнение стека, есть ли у кого-нибудь решение. Вот и все. Я думаю, вы могли неправильно понять мой вопрос, но все подробности там.

scosman 21.05.2023 20:47

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

scosman 21.05.2023 20:53

«Я предполагаю, что внутри он довольно быстро знает, что вышел из строя» — неверное предположение, которое привело к вопросу, на который невозможно было ответить. Нет ошибки «распространить как можно скорее», но вы можете легко изменить тайм-ауты для различных фаз запроса. Если бы не было активного интерфейса, то сразу возвращалась бы ошибка. Если это не так, то это где-то ошибка, и для диагностики потребуется минимально воспроизводимый пример.

JimB 21.05.2023 20:55

Вы отвечаете не на заданный вопрос. Ваш собственный тест показал, что он может обнаружить это. Вопрос имеет минимальный пример, включая платформу. Ответ здесь «проблема с платформой», а не «она не может быстро обнаружить этот случай и ошибку».

scosman 21.05.2023 21:14
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
0
14
108
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Первоначальное рукопожатие TCP завершится ошибкой без подключенного сетевого интерфейса, и в этот момент шансов на успех нет. Что он делает следующие 19,99 секунды?

HTTP-клиент Go ожидает полного времени ожидания, прежде чем вернуть ошибку при отсутствии сетевого подключения, потому что клиент не знает о состоянии сети, когда он инициирует запрос. Он знает только, что не получил ответа в течение указанного периода ожидания.
Клиент ожидает, когда стек TCP операционной системы вернет ошибку, когда ему не удается установить соединение, и это может занять некоторое время из-за различных факторов, таких как конфигурация сети, реализация TCP/IP операционной системы и т. д.

Я не знаю прямого способа установить более быстрое время ожидания для случая, когда в HTTP-клиенте Go нет сетевого подключения.

Другим подходом может быть сочетание использования контекста с тайм-аутом и отдельной горутиной, которая проверяет состояние подключения к сети. Горутина отменит контекст, если обнаружит отсутствие сетевого подключения. В запросе будет использоваться NewRequestWithContext

Например:

ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()

// Check network connection in a separate goroutine.
go func() {
    if !isNetworkAvailable() {
        cancel()
    }
}()

req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
    log.Fatal(err)
}

response, err := client.Do(req)
if err != nil {
    log.Fatal(err)
}

Вам нужно будет реализовать isNetworkAvailable(): функцию для проверки наличия сетевого подключения. Это может зависеть от платформы, и в iOS вам, вероятно, потребуется использовать системный API для проверки состояния сети.

Обратите внимание, что этот подход может работать не во всех сценариях. Например, сеть может стать доступной сразу после того, как isNetworkAvailable() проверит ее, но до того, как будет сделан HTTP-запрос. В этом случае запрос будет отменен, даже если сеть доступна. Но это может помочь в тех случаях, когда известно, что сеть недоступна в течение длительного периода времени.


Например:

import (
    "net"
    "time"
)

func isNetworkAvailable() bool {
    timeout := 2 * time.Second
    conn, err := net.DialTimeout("tcp", "8.8.8.8:53", timeout)
    if err != nil {
        return false
    }
    if conn != nil {
        defer conn.Close()
    }
    return true
}

Эта функция пытается установить TCP-соединение с 8.8.8.8 через порт 53 (порт, используемый DNS-серверами) с тайм-аутом в 2 секунды.
(как JimB добавляет в комментарии: «Все, что вы можете сделать, это попытаться сделать несколько сетевых вызовов с более короткими тайм-аутами»)

Если функция не может установить соединение в течение этого времени, она предполагает, что сетевое соединение отсутствует, и возвращает false.
Если он может установить соединение, он закрывает соединение и возвращает значение true.

Если сети вообще нет, вызов DNS или подключения немедленно завершится ошибкой, как и с любым другим языком. Они не имеют ничего конкретного, чтобы пойти в этой ситуации. Все, что вы можете сделать, это попытаться сделать несколько сетевых вызовов с более короткими тайм-аутами.

JimB 21.05.2023 15:45

@JimB Хороший вопрос. Это дало мне (грубую) идею для isNetworkAvailable()

VonC 21.05.2023 16:49

Обычно мы просто устанавливаем более короткие тайм-ауты для начальных частей запроса, таких как набор номера, рукопожатие, заголовки ответа и т. д., которые, как мы можем предположить, будут надежными индикаторами, основанными на ситуации, но проверка, подобная вашему предложению, также действительна.

JimB 21.05.2023 17:34
Ответ принят как подходящий

Ответ здесь оказывается довольно простым: внутренние компоненты golang могут и действительно знают, когда нет сети, и своевременно распространяют ошибку, не дожидаясь 20-секундного тайм-аута. По сети ничего не отправляется, и ждать нечего. Похоже, Go все делает правильно, и в пример кода не нужно вносить никаких изменений.

Проблема по-прежнему постоянно воспроизводится, но только на симуляторе iOS. Похоже, это проблема, связанная с тем, как симулятор iOS сопоставляет соединения с хост-ОС. Не уверен, что это давняя проблема или разовая проблема в моей паре MacOS/симулятор. На хосте MacOS и реальных устройствах iOS он работает правильно, тайм-аут немедленно, когда нет сетевого интерфейса.

Нет необходимости в дополнительном запросе, так как это просто еще один путь к тому же выводу, который добавляет возможность других сбоев. Может быть полезно отличать проблемы с сетью от проблем с конкретной службой или быстрее получать индикатор реального состояния сети (после наличия подключенного сетевого интерфейса).

Другие вопросы по теме