使用Golang的Context管理上下文的方法
golang 1.7版本中context庫被很多標準庫的模組所使用,比如net/http和os的一些模組中,利用這些原生模組,我們就不需要自己再寫上下文的管理器了,直接呼叫函式介面即可實現,利用context我們可以實現一些比如請求的宣告週期內的變數管理,執行一些操作的超時等等。
儲存上下文物件
這裡我們通過一個簡單的例子來看一下如何使用context的特性來實現上下文的物件儲存,這裡我們寫了一個簡單的http server,具有登入和退出,狀態檢查路由(檢查使用者是否登入)
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中,所有的請求都必須先經過該中介軟體後才能進入各自的路由處理中。具體的實現程式碼如下:
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為空的話則繼續傳遞到對應的路由處理函式中,否則儲存cookie的值到Context,注意這裡的Context()是request物件的方法,將建立一個新的上下文物件(如果context為空),context.WithValue()函式將key和value儲存在新的上下文物件中並返回該物件。
其餘的路由處理函式程式碼如下,分別用來儲存cookie的登入路由LoginHandler(),還有刪除cookie的退出路由處理函式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,-1) cookie := http.Cookie{Name: "username",Value: "[email protected]",Expires: expiration} http.SetCookie(w,&cookie) }
這裡我們在請求/login的時候,可以傳遞一個引數username到函式中,比如/login?username=alice,預設為”guest”使用者. 設定的cookie有效期為1天,刪除的時候我們只需要設定cookie為之前的日期即可。
另外一個關鍵的部分是讀取上下文儲存內容的 StatusHandler() 路由處理函式,該函式將呼叫r.Context()獲得request的上下文,如果我們執行了login後,那我們在中介軟體中已經設定了該物件,所以請求將檢視是否上下文物件中儲存了一個名為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")) } }
我們不僅僅可以在上下文中儲存簡單的型別,我們可以儲存任何型別的資料,因為Value() 返回的物件是一個interface{}物件,所以我們需要轉換一下才能使用。
超時處理
對於簡單的儲存和傳遞物件,使用context的確很方便,但是該庫的使用不僅僅是儲存變數,還可以建立一個超時和取消的行為,比如說我們web端去請求了其他的資源,但是該資源的處理比較耗時,我們無法預見什麼時候能夠返回,如果讓使用者超時的話,實在是不太好,所以我們需要建立一個超時的操作,主動判斷是否超時,然後傳遞一個合適的行為給使用者。
這裡我們現在路由中增加一個長期執行的job路由
mux.HandleFunc("/longjob",jobWithCancelHandler)
具體的處理如下,我們的handler會利用WithCancel() 返回一個新的(如果沒有建立)或者原來已儲存的上下文,還有一個cancel物件,這個物件可以用來手動執行取消操作。另外我們的url中可以指定這個任務模擬執行的長度,比如/longjob?jobtime=10則代表模擬的任務將會執行超過10秒。 執行任務的函式longRunningCalculation()返回一個chan該chan會在執行時間到期後寫入一個Done字串。
handler中我們就可以使用select語句監聽兩個非快取的channel,阻塞直到有資料寫到任何一個channel中。比如程式碼中我們設定了超時是5秒,而任務執行10秒的話則5秒到期後ctx.Done()會因為cancel()的呼叫而寫入資料,這樣該handler就會因為超時退出。否則的話則執行正常的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還有很多函式這裡沒有涉及,比如WithTimeout和WithDeadline等,但是使用上都比較相似,這裡就不在舉例。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。