我們還可以參考net/dail/DialContext
換而言之,如果你希望你實(shí)現(xiàn)的包是“可中止/可控制”的,那么你在你包實(shí)現(xiàn)的函數(shù)里面,最好是能接收一個(gè)Context函數(shù),并且處理了Context.Done。
場(chǎng)景二:PipeLine
pipeline模式就是流水線模型,流水線上的幾個(gè)工人,有n個(gè)產(chǎn)品,一個(gè)一個(gè)產(chǎn)品進(jìn)行組裝。其實(shí)pipeline模型的實(shí)現(xiàn)和Context并無(wú)關(guān)系,沒(méi)有context我們也能用chan實(shí)現(xiàn)pipeline模型。但是對(duì)于整條流水線的控制,則是需要使用上Context的。這篇文章Pipeline Patterns in Go的例子是非常好的說(shuō)明。這里就大致對(duì)這個(gè)代碼進(jìn)行下說(shuō)明。
runSimplePipeline的流水線工人有三個(gè),lineListSource負(fù)責(zé)將參數(shù)一個(gè)個(gè)分割進(jìn)行傳輸,lineParser負(fù)責(zé)將字符串處理成int64,sink根據(jù)具體的值判斷這個(gè)數(shù)據(jù)是否可用。他們所有的返回值基本上都有兩個(gè)chan,一個(gè)用于傳遞數(shù)據(jù),一個(gè)用于傳遞錯(cuò)誤。(<-chan string, <-chan error)輸入基本上也都有兩個(gè)值,一個(gè)是Context,用于傳聲控制的,一個(gè)是(in <-chan)輸入產(chǎn)品的。
我們可以看到,這三個(gè)工人的具體函數(shù)里面,都使用switch處理了case <-ctx.Done()。這個(gè)就是生產(chǎn)線上的命令控制。
func lineParser(ctx context.Context, base int, in <-chan string) (
<-chan int64, <-chan error, error) {
...
go func() {
defer close(out)
defer close(errc)
for line := range in {
n, err := strconv.ParseInt(line, base, 64)
if err != nil {
errc <- err
return
}
select {
case out <- n:
case <-ctx.Done():
return
}
}
}()
return out, errc, nil
}
場(chǎng)景三:超時(shí)請(qǐng)求
我們發(fā)送RPC請(qǐng)求的時(shí)候,往往希望對(duì)這個(gè)請(qǐng)求進(jìn)行一個(gè)超時(shí)的限制。當(dāng)一個(gè)RPC請(qǐng)求超過(guò)10s的請(qǐng)求,自動(dòng)斷開(kāi)。當(dāng)然我們使用CancelContext,也能實(shí)現(xiàn)這個(gè)功能(開(kāi)啟一個(gè)新的goroutine,這個(gè)goroutine拿著cancel函數(shù),當(dāng)時(shí)間到了,就調(diào)用cancel函數(shù))。
鑒于這個(gè)需求是非常常見(jiàn)的,context包也實(shí)現(xiàn)了這個(gè)需求:timerCtx。具體實(shí)例化的方法是 WithDeadline 和 WithTimeout。
具體的timerCtx里面的邏輯也就是通過(guò)time.AfterFunc來(lái)調(diào)用ctx.cancel的。
官方的例子:
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
}
}
在http的客戶端里面加上timeout也是一個(gè)常見(jiàn)的辦法
uri := "https://httpbin.org/delay/3"
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
log.Fatalf("http.NewRequest() failed with '%s'\\\\n", err)
}
ctx, _ := context.WithTimeout(context.Background(), time.Millisecond*100)
req = req.WithContext(ctx)
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatalf("http.DefaultClient.Do() failed with:\\\\n'%s'\\\\n", err)
}
defer resp.Body.Close()
在http服務(wù)端設(shè)置一個(gè)timeout如何做呢?
package main
import (
"net/http"
"time"
)
func test(w http.ResponseWriter, r *http.Request) {
time.Sleep(20 * time.Second)
w.Write([]byte("test"))
}
func main() {
http.HandleFunc("/", test)
timeoutHandler := http.TimeoutHandler(http.DefaultServeMux, 5 * time.Second, "timeout")
http.ListenAndServe(":8080", timeoutHandler)
}
我們看看TimeoutHandler的內(nèi)部,本質(zhì)上也是通過(guò)context.WithTimeout來(lái)做處理。
func (h *timeoutHandler) ServeHTTP(w ResponseWriter, r *Request) {
...
ctx, cancelCtx = context.WithTimeout(r.Context(), h.dt)
defer cancelCtx()
...
go func() {
...
h.handler.ServeHTTP(tw, r)
}()
select {
...
case <-ctx.Done():
...
}
}
場(chǎng)景四:HTTP服務(wù)器的request互相傳遞數(shù)據(jù)
context還提供了valueCtx的數(shù)據(jù)結(jié)構(gòu)。
這個(gè)valueCtx最經(jīng)常使用的場(chǎng)景就是在一個(gè)http服務(wù)器中,在request中傳遞一個(gè)特定值,比如有一個(gè)中間件,做cookie驗(yàn)證,然后把驗(yàn)證后的用戶名存放在request中。
我們可以看到,官方的request里面是包含了Context的,并且提供了WithContext的方法進(jìn)行context的替換。
package main
import (
"net/http"
"context"
)
type FooKey string
var UserName = FooKey("user-name")
var UserId = FooKey("user-id")
func foo(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), UserId, "1")
ctx2 := context.WithValue(ctx, UserName, "yejianfeng")
next(w, r.WithContext(ctx2))
}
}
func GetUserName(context context.Context) string {
if ret, ok := context.Value(UserName).(string); ok {
return ret
}
return ""
}
func GetUserId(context context.Context) string {
if ret, ok := context.Value(UserId).(string); ok {
return ret
}
return ""
}
func test(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("welcome: "))
w.Write([]byte(GetUserId(r.Context())))
w.Write([]byte(" "))
w.Write([]byte(GetUserName(r.Context())))
}
func main() {
http.Handle("/", foo(test))
http.ListenAndServe(":8080", nil)
}
在使用ValueCtx的時(shí)候需要注意一點(diǎn),這里的key不應(yīng)該設(shè)置成為普通的String或者Int類型,為了防止不同的中間件對(duì)這個(gè)key的覆蓋。最好的情況是每個(gè)中間件使用一個(gè)自定義的key類型,比如這里的FooKey,而且獲取Value的邏輯盡量也抽取出來(lái)作為一個(gè)函數(shù),放在這個(gè)middleware的同包中。這樣,就會(huì)有效避免不同包設(shè)置相同的key的沖突問(wèn)題了。
更多關(guān)于云服務(wù)器,域名注冊(cè),虛擬主機(jī)的問(wèn)題,請(qǐng)?jiān)L問(wèn)西部數(shù)碼官網(wǎng):m.ps-sw.cn