Intro
關於 API Timeout 的網路資料略顯不足,Wikipedia 目前也只有廣義 Timeout (computing) 資訊。
本篇將分享個人對 Timeout 的所知,如有錯誤歡迎告知修正
Timeout 定義
Client | Connect Timeout | Read Timeout |
---|---|---|
TCP Client | TCP handshake 中從發出 SYN 後, 至等待收到 SYN-ACK 的時間 |
送出 PSH-ACK packet, 至等待收到對應 PSH-ACK packet 的時間 |
HTTP Client | 從 DNS lookup (若有) 或 TCP handshake 中從發出 SYN 後, 至等待收到 SYN-ACK 的時間 (是否包含 DNS lookup 視套件而定) |
送出最後 HTTP request packet, 至等待收到最後 response packet 的時間 |
HTTPS Client | 從 DNS lookup (若有) 或 TCP handshake 中從發出 SYN 後, 至等待收到最後 TLS handshake 的時間 (是否包含 DNS lookup / TLS 交握視套件而定) |
送出最後 HTTP request packet, 至等待收到最後 response packet 的時間 |
- API timeout = Max timeout = Connect timeout + Read timeout
Programing
cURL
cURL 的 connect timeout 與 read timeout 會共用錯誤碼 28
,但錯誤訊息內容不同,可藉此區分。
// connect timeout = 5, max timeout (connect+read) = 25
$ curl -v --connect-timeout 5 -m 25 "https://yourname.com"
// Result within connection timeout
curl: (28) Connection timed out after 5004 milliseconds
// Result within read timeout
curl: (28) Operation timed out after 25005 milliseconds with 0 bytes received
CURL - Timeouts · Everything curl
--connect-timeout - curl - How To Use: The connection phase is considered complete when the DNS lookup and requested TCP, TLS or QUIC handshakes are done.
Python
import requests
connect_timeout = 5; read_timeout = 20
try:
response = requests.get("https://your.name", allow_redirects=False, timeout=(connect_timeout, read_timeout))
except requests.exceptions.ConnectTimeout:
print("Connection Timeout")
except requests.exceptions.ReadTimeout:
print("Read Timeout")
PHP
PHP 是基於 OS 底層 CURL 函式庫作為 HTTP Client,可依照 CURL 的特性設定 timeout 並區分不同的 exceptions:
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://example.com',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 25,
]);
curl_exec($ch);
$errno = curl_errno($ch); // Error code
$error = curl_error($ch); // Error message
curl_close($ch);
if ($errno === CURLE_OPERATION_TIMEOUTED) { // Code 28
if (str_contains($error, 'Connection timed out')) {
echo "Connection timeout";
} elseif (str_contains($error, 'Operation timed out')) {
echo "Read timeout";
} else {
echo "Timeout occurred: $error";
}
} elseif ($errno !== 0) {
echo "❗ Other cURL error: $error\n";
} else {
echo "✅ Request succeeded\n";
}
PHP with Guzzle
截至目前為止,Guzzle 的 Error Handler 並未進一步區分 cURL 中的 Connect Timeout 與 Read Timeout。
因此,當 cURL 回傳錯誤碼 CURLE_OPERATION_TIMEOUTED(代碼 28)時,Guzzle 會一律拋出 ConnectException。
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
$client = new Client();
try {
$response = $client->request('GET', 'https://your.name/', [
'connect_timeout' => 5, // Connect timeout in seconds
'timeout' => 25, // Total (Connect + read) timeout in seconds
]);
} catch (ConnectException $e) {
$message = $e->getMessage();
if (str_contains($message, 'Connection timed out')) {
return 'connect_timeout';
}
if (str_contains($message, 'Operation timed out')) {
return 'read_timeout';
}
return 'unknown_timeout';
}
JAVA with Apache HttpClient
根據目前官方文件,Apache HttpClient 的 Connect Timeout 主要涵蓋 TCP 連線建立階段,通常不包含 TLS 握手;TLS 握手階段的逾時則包含在 Socket Timeout 內。直到 5.x 版本,HttpClient 才新增獨立的 Response Timeout 用於控制伺服器回應的等待時間 (TBC)。
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.apache.http.conn.ConnectTimeoutException;
import java.net.SocketTimeoutException;
import java.io.IOException;
public class ApacheHttpClient {
public static void main(String[] args) {
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(5000) // Connect timeout
.setSocketTimeout(20000) // (包含 TLS 握手、HTTP 回應等)
.build();
try (final CloseableHttpClient httpclient =
HttpClientBuilder.create().setDefaultRequestConfig(config).build()) {
final HttpGet httpget = new HttpGet("https://your.name");
try (CloseableHttpResponse response = httpclient.execute(httpget)) {
HttpEntity entity = response.getEntity();
if (entity != null) {
String content = EntityUtils.toString(entity);
System.out.println(content);
}
}
} catch (ConnectTimeoutException e) {
// Handle connect timeout
e.printStackTrace();
} catch (SocketTimeoutException e) {
// Handle socket timeout (包含 TLS 握手、HTTP 回應等)
e.printStackTrace();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
- v 4.x - getConnectTimeout: Determines the timeout in milliseconds until a connection is established. (可能不含 TLS handshake)
- v 4.x - getSocketTimeout: Defines the socket timeout (SO_TIMEOUT) in milliseconds, which is the timeout for waiting for data or, put differently, a maximum period inactivity between two consecutive data packets). (可能包含 TLS handshake)
- v 5.5.x - setConnectTimeout: Determines the timeout until a new connection is fully established. This may also include transport security negotiation exchanges such as SSL or TLS protocol negotiation).
- v 5.5.x - setResponseTimeout: Determines the timeout until arrival of a response from the opposite endpoint.
定義比較表
語言 / 工具 | Connect timeout 是否包含 TLS? | 是否可單獨控制 TLS timeout? |
---|---|---|
curl |
✅ 包含 | ❌ 無法細分 |
Python requests |
✅ 包含 | ❌ 無法細分 |
Go net/http |
✅ 包含 | ✅ 可獨立設定 TLSHandshakeTimeout |
Node.js axios |
✅ 包含 | ❌ 無法細分 |
Node.js https 模組 |
✅ 包含 | ❌ 無法細分 |
Java HttpClient 4.x | ❌ 不含(TLS 在 read/socket timeout) | ❌ 無法細分 |
Java HttpClient 5.x | ❌ 不含(TLS 在 read/socket timeout) | ✅ 可透過 Response Timeout 控制 |