HTTP客户端:在没有网络时更快的超时时间

huangapple go评论76阅读模式
英文:

http client: faster timeout when no network

问题

在Go语言中进行HTTP Get请求时,即使没有网络连接,它也会等待完整的超时时间才返回错误。

我猜在内部,它很快就知道请求失败了;我希望错误能尽快传播上来,而不是等待超时时间。当网络存在但速度很慢时,我确实希望它尝试20秒。我该如何设置一个具有这种行为的客户端?

以下是代码示例:

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

response, err := client.Get(url)

如果有关系的话,我正在使用gomobile,并且它正在运行在iOS模拟器上。

英文:

When making a http Get request in Go, it waits the full timeout time before returning an error, even when there is no network connection.

I assume in the internals it knows pretty quickly it has failed; I'd like that error to propagate up as soon as possible instead of waiting for the timeout time. I do want it to try for 20s when the network is there and just slow. How can I setup a client with this behaviour?

Code to see issue:

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

response, err := client.Get(url)

If it matters I'm using gomobile and it's running on the iOS simulator.

答案1

得分: 1

初始的TCP握手在没有连接的网络接口的情况下会失败,此时没有成功的机会。接下来的19.99秒它在做什么?

Go的HTTP客户端在没有网络连接时会等待完整的超时时间,然后返回错误,因为客户端在发起请求时不知道网络的状态,它只知道在指定的超时时间内没有收到响应。客户端正在等待操作系统的TCP堆栈返回错误,因为它无法建立连接,这可能需要一些时间,因为涉及到各种因素,如网络配置、操作系统的TCP/IP实现等。

我不知道在Go的HTTP客户端中如何直接设置没有网络连接时的更快超时时间。

另一种方法是结合使用具有超时的上下文(context)和一个单独的goroutine来检查网络连接状态。如果检测到没有网络连接,goroutine会取消上下文。请求将使用NewRequestWithContext

例如:

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

// 在单独的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
}

该函数尝试在2秒的超时时间内与8.8.8.8的53端口(DNS服务器使用的端口)建立TCP连接。如果函数在超时时间内无法建立连接,则假设没有网络连接并返回false。如果可以建立连接,则关闭连接并返回true。

英文:

The initial TCP handshake will fail without a connected network interface, and at that point there's no chance of success. What's it doing for the next 19.99 seconds?

The Go's HTTP client waits for the full timeout duration before returning an error when there is no network connection, because the client does not know the state of the network when it initiates the request. It only knows that it has not received a response within the specified timeout period.
The client is waiting for the operating system's TCP stack to return an error when it fails to establish a connection, and that can take some time because of various factors like the network configuration, the operating system's TCP/IP implementation, etc.

I do not know of a direct way to set a faster timeout for the case where there's no network connection in Go's HTTP client.

Another approach would be to combining the use of a context with a timeout and a separate goroutine that checks the network connectivity status. The goroutine would cancel the context if it detects that there's no network connection.
The request would use NewRequestWithContext

For instance:

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)
}

You would need to implement isNetworkAvailable(): a function to check if there is a network connection available. This can be platform-specific, and on iOS you would likely need to use a system API to check the network status.

Note that this approach might not work perfectly in all scenarios. For example, the network could become available just after isNetworkAvailable() checks it, but before the HTTP request is made. In this case, the request would be canceled even though the network is available. But it might help in cases where the network is known to be unavailable for a longer period.


For instance:

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
}

This function tries to establish a TCP connection to 8.8.8.8 on port 53 (the port used by DNS servers) with a timeout of 2 seconds.
(as JimB adds in the comments: "All you can do is try to make some network calls with shorter timeouts")

If the function can't establish a connection within that time, it assumes there's no network connection and returns false.
If it can establish a connection, it closes the connection and returns true.

答案2

得分: -1

这里的答案非常简单:Golang内部可以知道并且确实知道何时没有网络,并且及时地传播失败,而不需要等待20秒的超时。没有任何数据发送到网络上,也没有需要等待的内容。Go似乎在做一切正确,示例代码不需要进行任何更改。

这个问题在iOS模拟器上一直存在,但似乎是特定于iOS模拟器将连接映射到主机操作系统的方式的问题。不确定这是否是一个长期存在的问题,还是只在我的MacOS/模拟器配对上出现的一次性问题。在主机的MacOS和真实的iOS设备上,当没有网络接口时,它会立即超时。

不需要额外的请求,因为那只是达到相同结论的另一种方式,这会增加其他故障的可能性。可能有助于区分网络问题和特定服务问题,或者更早地获取真实网络状态的指示器(超过已连接的网络接口的存在)。

英文:

The answer here ends up being quite simple: golang internals can and does know when there is no network, and propagates up the failure in a timely manner without waiting for the 20s timeout. Nothing is sent over the network, and there's nothing to wait on. Go seems to be doing everything properly, and there's no changes needed to the sample code.

The issue still reproduces consistently, but only on the iOS simulator. It seems to be an issue specific to how the iOS simulator mapped connections to the host OS. Not sure if this is a longstanding issue, or a one-off on my MacOS/simulator pairing. On the host MacOS and real iOS devices it works properly, timing out immediately, when there's no network interface.

There's no need for an extra request, as that's just another path to same conclusion, which adds the possibility of other failures. Might be helpful to differentiate network issues from issues with specific service, or get an indicator of real network status (past the existence of a connected network interface) sooner.

huangapple
  • 本文由 发表于 2023年5月21日 02:06:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/76296690.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定