Go http.Client
發送附帶context的請求時若得到回應前發生context超時、context取消或超過client時間限制會導致請求失敗。
範例環境:
- Go 1.18
範例
下面在GET()
函式中呼叫http.NewRequestWithContext()
建立帶context及指定url的GET請求物件http.Request
,然後建立http.Client
設定回應限時3秒,最後將請求物件傳入http.Client.Do()
發出請求。
main.go
package main
import (
"context"
"fmt"
"io"
"log"
"net/http"
"time"
)
func main() {
...
}
func Get(ctx context.Context, url string) (string, error) {
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
url,
nil)
if err != nil {
log.Printf("create request error, err=%v", err)
return "", err
}
client := &http.Client{
Timeout: time.Second * 3,
}
resp, err := client.Do(req) // 發送請求
if err != nil {
log.Printf("client send request error, err=%v", err)
return "", err
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
log.Printf("read request body error, err=%v", err)
return "", err
}
return string(b), nil
}
測試
在測試程式main_test.go
中測試下面幾種狀況:
- context deadline exceeded - context超過期限
- context canceled - context已取消
- context deadline exceeded (Client.Timeout exceeded while awaiting headers) - Client等待headers超過期限
context deadline exceeded
TestGet_ContextDeadlineExceeded()
中使用context.WithTimeout(ctx, time.Second*1)
設定context期限1秒,並在mock server用time.Sleep(time.Second * 2)
延遲2秒才回應。由於context會先到期,所以http.Client.Do()
回傳錯誤context deadline exceeded
。
main_test.go
package main
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"time"
)
...
func TestGet_ContextDeadlineExceeded(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
time.Sleep(time.Second * 2)
w.Write([]byte("hello"))
}))
defer ts.Close()
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, time.Second*1)
defer cancel()
_, err := Get(ctx, ts.URL)
if err == nil {
t.Error("unexpected success")
}
}
...
執行測試如下:
$ go test -run TestGet_ContextDeadlineExceeded -v
=== RUN TestGet_ContextDeadlineExceeded
2022/06/01 23:53:21 client send request error, err=Get "http://127.0.0.1:53771": context deadline exceeded
--- PASS: TestGet_ContextDeadlineExceeded (2.01s)
PASS
ok abc.com/demo 2.394s
context canceled
TestGet_ContextCanceled()
中使用context.WithCancel()
設定context取得context.CancelFunc
並馬上執行(取消)。在mock server用time.Sleep(time.Second * 2)
延遲2秒才回應。由於context先被取消,所以http.Client.Do()
回傳錯誤context canceled
。
main_test.go
package main
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"time"
)
...
func TestGet_ContextCanceled(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
time.Sleep(time.Second * 2)
w.Write([]byte("hello"))
}))
defer ts.Close()
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
cancel()
_, err := Get(ctx, ts.URL)
if err == nil {
t.Error("unexpected success")
}
}
...
執行測試如下:
$ go test -run TestGet_ContextCanceled -v
=== RUN TestGet_ContextCanceled
2022/06/01 23:59:17 client send request error, err=Get "http://127.0.0.1:53802": context canceled
--- PASS: TestGet_ContextCanceled (0.00s)
PASS
ok abc.com/demo 0.393s
context deadline exceeded (Client.Timeout exceeded while awaiting headers)
TestGet_ClientTimeout()
中在mock server用time.Sleep(time.Second * 5)
延遲5秒才回應。由於受測的Get()
中Client.Timeout
設為限時3秒回應,所以http.Client.Do()
回傳錯誤context deadline exceeded (Client.Timeout exceeded while awaiting headers)
。
main_test.go
package main
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"time"
)
...
func TestGet_ClientTimeout(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
time.Sleep(time.Second * 5)
w.Write([]byte("hello"))
}))
defer ts.Close()
_, err := Get(context.Background(), ts.URL)
if err == nil {
t.Error("unexpected success")
}
}
執行測試如下:
$ go test -run TestGet_ClientTimeout -v
=== RUN TestGet_ClientTimeout
2022/06/02 00:06:07 client send request error, err=Get "http://127.0.0.1:53844": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
--- PASS: TestGet_ClientTimeout (5.00s)
PASS
ok abc.com/demo 5.325s
沒有留言:
張貼留言