golang基礎-網路請求WithTimeout、上下文withValue、withCancel、WithDeadline
阿新 • • 發佈:2019-01-04
在 Go http包的Server中,每一個請求在都有一個對應的 goroutine 去處理。請求處理函式通常會啟動額外的 goroutine 用來訪問後端服務,比如資料庫和RPC服務。用來處理一個請求的 goroutine 通常需要訪問一些與請求特定的資料,比如終端使用者的身份認證資訊、驗證相關的token、請求的截止時間。 當一個請求被取消或超時時,所有用來處理該請求的 goroutine 都應該迅速退出,然後系統才能釋放這些 goroutine 佔用的資源。
在Google 內部,開發了 Context 包,專門用來簡化 對於處理單個請求的多個 goroutine 之間與請求域的資料、取消訊號、截止時間等相關操作,這些操作可能涉及多個 API 呼叫。你可以通過 go get golang.org/x/net/context 命令獲取這個包。本文要講的就是如果使用這個包,同時也會提供一個完整的例子。
Context interface
網路請求超時控制
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Deadline()返回一個time.Time,是當前 Context 的應該結束的時間,ok 表示是否有 deadline Done()返回一個struct{}型別的只讀 channel Err()返回 Context 被取消時的錯誤 Value(key interface{}) 是 Context 自帶的 K-V 儲存功能
package main
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"time"
)
type Result struct {
r *http.Response
err error
}
func process() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
//釋放資源
defer cancel()
tr := &http.Transport{}
client := &http.Client{Transport: tr}
resultChan := make (chan Result, 1)
//發起請求
req, err := http.NewRequest("GET", "http://www.baidu.com", nil)
if err != nil {
fmt.Println("http request failed, err:", err)
return
}
/*
func (c *Client) Do(req *Request) (*Response, error)
*/
go func() {
resp, err := client.Do(req)
pack := Result{r: resp, err: err}
//將返回資訊寫入管道(正確或者錯誤的)
resultChan <- pack
}()
select {
case <-ctx.Done():
tr.CancelRequest(req)
er:= <-resultChan
fmt.Println("Timeout!",er.err)
case res := <-resultChan:
defer res.r.Body.Close()
out, _ := ioutil.ReadAll(res.r.Body)
fmt.Printf("Server Response: %s", out)
}
return
}
func main() {
process()
}
輸出如下:
如果修改下程式碼
req, err := http.NewRequest("GET", "http://google.com", nil)
請求超時,輸出log資訊如下
PS E:\golang\go_pro\src\safly> go build main.go
PS E:\golang\go_pro\src\safly> main.exe
Timeout! Get http://google.com: net/http: request canceled while waiting for connection
PS E:\golang\go_pro\src\safly>
上下文WithValue
package main
import (
"context"
"fmt"
)
func process(ctx context.Context) {
ret,ok := ctx.Value("trace_id").(int)
if !ok {
ret = 21342423
}
fmt.Printf("ret:%d\n", ret)
s , _ := ctx.Value("session").(string)
fmt.Printf("session:%s\n", s)
}
func main() {
ctx := context.WithValue(context.Background(), "trace_id", 13483434)
ctx = context.WithValue(ctx, "session", "sdlkfjkaslfsalfsafjalskfj")
process(ctx)
}
輸出如下:
PS E:\golang\go_pro\src\safly> go run main.go
ret:13483434
session:sdlkfjkaslfsalfsafjalskfj
PS E:\golang\go_pro\src\safly>
超時控制WithDeadline
package main
import (
"context"
"fmt"
"time"
)
func main() {
d := time.Now().Add(4 * time.Second)
//50毫秒到了,觸發如下程式碼
ctx, cancel := context.WithDeadline(context.Background(), d)
defer cancel()
select {
case <-time.After(3 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
//50毫秒到了,執行該程式碼
fmt.Println(ctx.Err())
}
}
輸出如下:
PS E:\golang\go_pro\src\safly> go run deadline.go
overslept
PS E:\golang\go_pro\src\safly>
如果將上面程式碼修改為
func main() {
d := time.Now().Add(4 * time.Second)
//50毫秒到了,觸發如下程式碼
ctx, cancel := context.WithDeadline(context.Background(), d)
defer cancel()
select {
case <-time.After(5 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
//50毫秒到了,執行該程式碼
fmt.Println(ctx.Err())
}
}
然後在執行輸出如下:
PS E:\golang\go_pro\src\safly> go run deadline.go
context deadline exceeded
PS E:\golang\go_pro\src\safly>
WithCancel
我們來了解一個利用context結束goroutine的demo
package main
import (
"context"
"fmt"
"time"
)
/*
建立一個管道chan,啟動goroutine
for迴圈存資料
**/
func gen(ctx context.Context) <-chan int {
dst := make(chan int)
n := 1
go func() {
for {
select {
case <-ctx.Done():
//執行defer cancel操作後,就會執行到該select入庫
fmt.Println("i exited")
return // returning not to leak the goroutine
case dst <- n:
n++
}
}
}()
return dst
}
func test() {
ctx, cancel := context.WithCancel(context.Background())
//當取資料n == 5時候,執行defer cancel操作
defer cancel()
intChan := gen(ctx)
for n := range intChan {
fmt.Println(n)
if n == 5 {
break
}
}
}
func main() {
test()
time.Sleep(time.Hour)
}
輸出如下:
PS E:\golang\go_pro\src\safly> go run cancle.go
1
2
3
4
5
i exited