天天看點

一個神秘現象引發對beego架構的思考

小強最近在項目中遇到了一個很奇怪的問題:在整改日志規範時,為了避免影響現有的代碼結構以及改動盡可能小的前提下,在調用記日志的SDK處将某一個字段值首字母改為大寫,代碼示例如下:

fmt.Println("--------SayHello begin------------")
  //項目中這裡的a實際是作為參數傳入,隻是可能為空串,不為空串,這樣寫肯定沒問題
  a := ""
  b := strings.ToUpper(a[:1]) + a[1:]
  fmt.Println("b is ", b)
  fmt.Println("--------SayHello end------------")

  this.Ctx.Output.Body(this.Ctx.Input.RequestBody)           

複制

項目中這裡的a變量其實是作為參數傳入,隻是可能為空串。a變量不為空串時,這樣寫肯定沒問題。但是當為空串時,即""時,就會出問題,在java中,運作的時候肯定會報一個“數組下表越界”的異常。小強将工程編譯後生成二進制檔案,放到伺服器上跑,測試修改後的日志是否符合規範,驗了一遍,沒有問題,然後就将代碼送出了。

之後版本出來測試時發現,有個奇怪的現象:接口不傳回任何東西,狀态碼依然是 200 OK。這讓小強很納悶兒,還好,我們的小強經驗豐富,還是解決過大bug的人,然後就根據接口走了一遍代碼流程,眉頭一皺,就知道問題所在了。原來就是a變量有時候傳進來是空字元串,導緻出現了slice下标越界的panic,說幹就幹,小強趕緊做了空串的判斷邏輯,重新驗了一把,問題就解決了。

小強是愛思考的孩子,不止要解決問題,也要知其是以然。小強在想,出現了panic咋日志裡面啥都不打呢,而且還傳回200,甚是疑惑。然後就在網上查資料,然後自己又看了beego的源碼,就明白了。不得不說,開源就是好啊。

原來問題是這樣,小強項目中使用的beego版本是1.6.1版。

小強查到了beego的錯誤處理流程:beego通過beego.App.Server.Handler處理所有的HTTP請求,在beego.Run()函數中,這個Handler就被設定為app.Handlers,可以參見beego1.6.1版本app.go的第95行:

app.Server.Handler = app.Handlers           

複制

而app在一開始就被初始化,可以看app.go中的init()函數,其中調用了NewApp()函數:

// NewApp returns a new beego application.
func NewApp() *App {
  cr := NewControllerRegister()
  app := &App{Handlers: cr, Server: &http.Server{}}
  return app
}           

複制

可以看出,把cr指派給Handler,其實cr是ControllerRegister類型,ControllerRegister類型實作了http.Handler接口,具體實作可以看router.go的第600行ServeHTTP方法。該方法中(第612行)有如下語句:

defer p.recoverPanic(context)           

複制

golang語言的錯誤處理機制是,當在某處調用panic(string)後,panic之後的語句将不再執行,而是通過調用關系逐級退出,在每一級調用處都通過defer處理函數檢查是否panic被recover()函數捕獲處理,如果沒有則繼續往上扔panic資訊,如果已經被捕獲則結束此次panic過程,由捕獲panic的函數處繼續往下執行。

出現異常會執行recoverPanic方法,該方法中(第864行)有這樣的代碼段:

if BConfig.RunMode == DEV {
        showErr(err, context, stack)
}           

複制

showErr函數中會對錯誤進行模闆渲染,而小強項目早在現網中投入使用,RunMode為prod,而非dev,是以recover()後不會有錯誤提示。

當RunMode為prod時:

一個神秘現象引發對beego架構的思考

當RunMode為prod時:

一個神秘現象引發對beego架構的思考

dev模式好歹會傳回錯誤資訊:slice bounds out of range

prod模式沒有任何提示。下标越界這種問題看似簡單,但是真正遇到了有時候也會摸不着頭腦。