golang 1.7版本中context庫被很多標準庫的模塊所使用,比如net/http和os的一些模塊中,利用這些原生模塊,我們就不需要自己再寫上下文的管理器了,直接調(diào)用函數(shù)接口即可實現(xiàn),利用context我們可以實現(xiàn)一些比如請求的聲明周期內(nèi)的變量管理,執(zhí)行一些操作的超時等等。
保存上下文對象
這里我們通過一個簡單的例子來看一下如何使用context的特性來實現(xiàn)上下文的對象保存,這里我們寫了一個簡單的http server,具有登錄和退出,狀態(tài)檢查路由(檢查用戶是否登錄)
func main(){
mux:=http.NewServeMux()
mux.HandleFunc("/",StatusHandler)
mux.HandleFunc("/login",LoginHandler)
mux.HandleFunc("/logout",LogoutHandler)
contextedMux:=AddContextSupport(mux)
log.Fatal(http.ListenAndServe(":8080",contextedMux))
}
其中的AddContextSupport是一個中間件,用來綁定一個context到原來的handler中,所有的請求都必須先經(jīng)過該中間件后才能進入各自的路由處理中。具體的實現(xiàn)代碼如下:
func AddContextSupport(next http.Handler)http.Handler{
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println(r.Method, "-", r.RequestURI)
cookie, _ := r.Cookie("username")
if cookie != nil {
ctx := context.WithValue(r.Context(), "username", cookie.Value)
// WithContext returns a shallow copy of r with its context changed
// to ctx. The provided ctx must be non-nil.
next.ServeHTTP(w, r.WithContext(ctx))
} else {
next.ServeHTTP(w, r)
}
})
}
該中間件可以打印每次請求的方法和請求的url,然后獲得請求的cookie值,如果cookie為空的話則繼續(xù)傳遞到對應的路由處理函數(shù)中,否則保存cookie的值到Context, 注意這里的Context()是request對象的方法, 將創(chuàng)建一個新的上下文對象(如果context為空),context.WithValue()函數(shù)將key和value保存在新的上下文對象中并返回該對象。
其余的路由處理函數(shù)代碼如下, 分別用來保存cookie的登錄路由LoginHandler(),還有刪除cookie的退出路由處理函數(shù)LogoutHandler()。
func LoginHandler(w http.ResponseWriter,r *http.Request){
expitation := time.Now().Add(24*time.Hour)
var username string
if username=r.URL.Query().Get("username");username==""{
username = "guest"
}
cookie:=http.Cookie{Name:"username",Value:username,Expires:expitation}
http.SetCookie(w,cookie)
}
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
expiration := time.Now().AddDate(0, 0, -1)
cookie := http.Cookie{Name: "username", Value: "alice_cooper@gmail.com", Expires: expiration}
http.SetCookie(w, cookie)
}
這里我們在請求/login的時候,可以傳遞一個參數(shù)username到函數(shù)中,比如/login?username=alice , 默認為”guest”用戶. 設置的cookie有效期為1天,刪除的時候我們只需要設置cookie為之前的日期即可。
另外一個關鍵的部分是讀取上下文保存內(nèi)容的 StatusHandler() 路由處理函數(shù),該函數(shù)將調(diào)用r.Context()獲得request的上下文,如果我們執(zhí)行了login后,那我們在中間件中已經(jīng)設置了該對象,所以請求將查看是否上下文對象中保存了一個名為username的對象,如果有的話則回應一個歡迎頁面。否則告知用戶沒有登錄。
func StatusHandler(w http.ResponseWriter,r *http.Request){
if username:=r.Context().Value("username"); username!=nil{
w.WriteHeader(http.StatusOK)
w.Write([]byte("Hi username:"+username.(string)+"\n"))
}else{
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("Not Logged in"))
}
}
我們不僅僅可以在上下文中保存簡單的類型,我們可以保存任何類型的數(shù)據(jù),因為Value() 返回的對象是一個interface{}對象,所以我們需要轉換一下才能使用。
超時處理
對于簡單的保存和傳遞對象,使用context的確很方便,但是該庫的使用不僅僅是保存變量,還可以創(chuàng)建一個超時和取消的行為,比如說我們web端去請求了其他的資源,但是該資源的處理比較耗時,我們無法預見什么時候能夠返回,如果讓用戶超時的話,實在是不太好,所以我們需要創(chuàng)建一個超時的操作,主動判斷是否超時,然后傳遞一個合適的行為給用戶。
這里我們現(xiàn)在路由中增加一個長期運行的job路由
mux.HandleFunc("/longjob",jobWithCancelHandler)
具體的處理如下,我們的handler會利用WithCancel() 返回一個新的(如果沒有創(chuàng)建)或者原來已保存的上下文,還有一個cancel對象,這個對象可以用來手動執(zhí)行取消操作。另外我們的url中可以指定這個任務模擬執(zhí)行的長度,比如/longjob?jobtime=10則代表模擬的任務將會執(zhí)行超過10秒。 執(zhí)行任務的函數(shù)longRunningCalculation()返回一個chan該chan會在執(zhí)行時間到期后寫入一個Done字符串。
handler中我們就可以使用select語句監(jiān)聽兩個非緩存的channel,阻塞直到有數(shù)據(jù)寫到任何一個channel中。比如代碼中我們設置了超時是5秒,而任務執(zhí)行10秒的話則5秒到期后ctx.Done()會因為cancel()的調(diào)用而寫入數(shù)據(jù),這樣該handler就會因為超時退出。否則的話則執(zhí)行正常的job處理后獲得傳遞的“Done”退出。
func longRunningCalculation(timeCost int)chan string{
result:=make(chan string)
go func (){
time.Sleep(time.Second*(time.Duration(timeCost)))
result-"Done"
}()
return result
}
func jobWithCancelHandler(w http.ResponseWriter, r * http.Request){
var ctx context.Context
var cancel context.CancelFunc
var jobtime string
if jobtime=r.URL.Query().Get("jobtime");jobtime==""{
jobtime = "10"
}
timecost,err:=strconv.Atoi(jobtime)
if err!=nil{
timecost=10
}
log.Println("Job will cost : "+jobtime+"s")
ctx,cancel = context.WithCancel(r.Context())
defer cancel()
go func(){
time.Sleep(5*time.Second)
cancel()
}()
select{
case -ctx.Done():
log.Println(ctx.Err())
return
case result:=-longRunningCalculation(timecost):
io.WriteString(w,result)
}
return
}
這就是使用context的一些基本方式,其實context還有很多函數(shù)這里沒有涉及,比如WithTimeout和WithDeadline等,但是使用上都比較相似,這里就不在舉例。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
您可能感興趣的文章:- GOLANG使用Context實現(xiàn)傳值、超時和取消的方法
- GOLANG使用Context管理關聯(lián)goroutine的方法
- 深入Golang之context的用法詳解