對 echo 架構進行統一的自訂錯誤處理

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

藉助移動端的增長,如今 RESTful 風格的 API 已經十分流行,
用各種語言去寫後端 API 都有很成熟方便的方案,用 golang 寫後端 API 更是生產力的代表,
你可以用不輸 python/ruby 這類動態語言的速度,寫出效能高出一兩個數量級的後端 API 。

ECHO 架構

由於 golang 的標準庫在網路方面已經很完善,導致架構發揮餘地不大。很多高手都說,
用什麼架構,用標準庫就寫好了,架構只是文法糖而已,還會限制項目的發展。
不過我們並不是高手,文法糖也是糖,用一個趁手的架構還是能提高不少效率的。
要是在半年前,你讓我推薦架構,我會說有很多,都各有優缺點,除了 beego 隨便選一個就可以。
但是來到2017年,一個叫 Echo 的架構脫穎而出。這是我目前最推薦的架構。
Echo 的宣傳語用的是 “高效能,易擴充,極簡 Go Web 架構” 。它的一些特性如所示:

這些特性裡,HTTP/2,Auto HTTPS,聽著很熟?這是我之前介紹的 Caddy 也有的特性,
因為 golang 實現這些太容易了。還有 Middleware 裡的一大堆功能也差不多。
我們在做微服務的時候,這些通用的東西由 API Gateway 統一實現就好了,
如果你寫的是個小的獨立應用的後端,這些開箱即用的功能倒是能提供很大的協助。

其實今天我主要想說說最後一個特性裡提到的,“中心化的 HTTP 錯誤處理”。

RESTful API 錯誤返回

一個團隊應當有一份 RESTful API 的規範,而在規範中應該規範響應格式,包括所有錯誤響應的格式。
比如微軟的規範,
jsonapi.org 推薦規範等等。
大部分時候我們不需要實現的那麼繁瑣,我們規定一個簡單的結構:

STATUS 400 Bad Request{  "error": "InvalidID",  "message": "invalid id in your url query parameters"}

傳統的錯誤響應可能只有一個伴隨 HTTP Status code 的 string 類型的 message,
如今我們把正常的響應格式變成了 JSON ,那麼把錯誤返回也用 JSON 吧。
除了用 JSON 之外,我們又增加了一個 error 欄位,
這個欄位是一個比 Status code 要詳細一個層級的 Key,
消費端可以用這個約定的 Key 做更為靈活的錯誤處理。

好了,我們就用這個簡單的例子進行下去,今天主題講的是 Echo 去統一處理的方法。

Echo 怎麼統一處理錯誤?

其實 Echo 的文檔雖然很漂亮,但是不夠詳細,深入一點的內容和例子並沒有。
但一個漂亮的 golang 項目,代碼即是文檔,我們應該有去 godoc.org 查文檔的習慣。
我們找到 Echo 的 GoDoc,
看 Echo 類型:

type Echo struct {    Server           *http.Server    TLSServer        *http.Server    Listener         net.Listener    TLSListener      net.Listener    DisableHTTP2     bool    Debug            bool    HTTPErrorHandler HTTPErrorHandler    Binder           Binder    Validator        Validator    Renderer         Renderer    AutoTLSManager   autocert.Manager    Mutex            sync.RWMutex    Logger           Logger    // contains filtered or unexported fields}

果然可以定義 HTTPErrorHandler, 順著找過去,

// HTTPErrorHandler is a centralized HTTP error handler.type HTTPErrorHandler func(error, Context)

它是一個傳入 error 和 Context 並且沒有傳回值的函數。
可是知道這些還是有點暈?並不知道怎麼寫這個函數啊。
沒關係,我這篇文章就是講怎麼寫這個函數的。往下看吧。

定義錯誤結構

由於 golang 是靜態類型,我們幹啥都需要先定義個結構,代碼如下:

type httpError struct {    code    int    Key     string `json:"error"`    Message string `json:"message"`}func newHTTPError(code int, key string, msg string) *httpError {    return &httpError{        code:    code,        Key:     key,        Message: msg,    }}// Error makes it compatible with `error` interface.func (e *httpError) Error() string {    return e.Key + ": " + e.Message}

這裡我們做了三件事

  1. 定義了錯誤的結構,其中包含 code,key 和 message,key 和 message 可以被匯出為 JSON。

  2. 做了個建立錯誤結構的函數,這樣就可以用一行代碼去建立一個錯誤了。

  3. 給這個結構增加了 Error 函數,這樣這個結構就成了一個 golang 的 error 介面。

處理錯誤

我們終於可以寫上文提到的自訂函數了,先看範例程式碼我再做解釋,然後你就能寫自己的了:

package mainimport (    "net/http"    "github.com/labstack/echo")// httpErrorHandler customize echo's HTTP error handler.func httpErrorHandler(err error, c echo.Context) {    var (        code = http.StatusInternalServerError        key  = "ServerError"        msg  string    )    if he, ok := err.(*httpError); ok {        code = he.code        key = he.Key        msg = he.Message    } else if config.Debug {        msg = err.Error()    } else {        msg = http.StatusText(code)    }    if !c.Response().Committed {        if c.Request().Method == echo.HEAD {            err := c.NoContent(code)            if err != nil {                c.Logger().Error(err)            }        } else {            err := c.JSON(code, newHTTPError(code, key, msg))            if err != nil {                c.Logger().Error(err)            }        }    }}

這個函數的功能就是根據傳進來的 error 和上下文 Context,組裝出合適的 HTTP 響應。
可因為 golang 的 error 是一個介面,也就是第一個參數可能傳進來任何奇怪的東西,
我們需要細心的處理一下。

第一部分我們定義了預設值作為最壞的情況,在 HTTP API 裡,消費端要是看到這種最壞的情況,
說明你要被扣獎金了,除非你可以甩鍋給你依賴的模組或基礎設施。

第二部分我們先看看傳進來的錯誤是不是我們之前定義的,如果是那就太好了。如果不是的話,
看來是一個其他的未知錯誤,如果 Debug 開著,那還好,不用扣獎金,我們把錯誤明細直接返回
到 msg 裡方便調試。如果也沒開 Debug ... 那隻好硬著頭皮返回 500 並什麼資訊都不給了。

第三部分你可以基本照抄,是檢查上下文中是否聲明這個響應已經提交了,只有沒提交的時候,
我們才需要把我們準備好的錯誤資訊以 JSON 格式提交,順便列印錯誤記錄檔。另外,如果請求
是 HEAD 方法的話,根據規範,你只能返回狀態 204 並默默在日誌記錄錯誤了。

應用

好了,我們寫好了統一的錯誤處理,該怎麼使用呢? 來看一個極簡的例子吧:

func getUser(c echo.Context) error {    var u user    id := c.Param("id")    if !bson.IsObjectIdHex(id) {        return newHTTPError(http.StatusBadRequest, "InvalidID", "invalid user id")    }    err := db.C("user").FindId(bson.ObjectIdHex(id)).One(&u)    if err == mgo.ErrNotFound {        return newHTTPError(http.StatusNotFound, "NotFound", err.Error())    }    if err != nil {        return err    }    return c.JSON(http.StatusOK, u)}

這是個從 mongodb 取 user 的例子,

  1. 檢查url中的id是不是一個合法的id,不是的話,返回我們之前自訂的錯誤。

  2. 去資料庫裡查,如果沒有記錄,返回 404 錯誤。

  3. 如果查詢資料庫的操作出了其他錯誤,這個時候我們無能為力了,只好直接把這個錯誤返回。

  4. 一切正常沒錯誤的話,我們返回狀態 200 和 JSON 資料。

我們可以看出,經過這麼一番折騰,在寫API的時候,省心了很多。
我們可以隨手用一行代碼構造錯誤,也可以直接把任何預測不到的錯誤返回,
不用再麻煩的每次去構造 500 錯誤了。

怎麼樣?快去安利小夥伴們用 echo 寫 HTTP API 吧,真的很方便。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.