借助移動(dòng)端的增長(zhǎng),如今 RESTful 風(fēng)格的 API 已經(jīng)十分流行贫悄,
用各種語(yǔ)言去寫(xiě)后端 API 都有很成熟方便的方案留美,用 golang 寫(xiě)后端 API 更是生產(chǎn)力的代表,
你可以用不輸 python/ruby 這類(lèi)動(dòng)態(tài)語(yǔ)言的速度镰烧,寫(xiě)出性能高出一兩個(gè)數(shù)量級(jí)的后端 API 。
ECHO 框架
由于 golang 的標(biāo)準(zhǔn)庫(kù)在網(wǎng)絡(luò)方面已經(jīng)很完善贮尉,導(dǎo)致框架發(fā)揮余地不大拌滋。很多高手都說(shuō)朴沿,
用什么框架猜谚,用標(biāo)準(zhǔn)庫(kù)就寫(xiě)好了,框架只是語(yǔ)法糖而已赌渣,還會(huì)限制項(xiàng)目的發(fā)展魏铅。
不過(guò)我們并不是高手,語(yǔ)法糖也是糖坚芜,用一個(gè)趁手的框架還是能提高不少效率的览芳。
要是在半年前,你讓我推薦框架鸿竖,我會(huì)說(shuō)有很多沧竟,都各有優(yōu)缺點(diǎn),除了 beego 隨便選一個(gè)就可以缚忧。
但是來(lái)到2017年悟泵,一個(gè)叫 Echo
的框架脫穎而出。這是我目前最推薦的框架闪水。
Echo 的宣傳語(yǔ)用的是 “高性能糕非,易擴(kuò)展,極簡(jiǎn) Go Web 框架” 球榆。它的一些特性如下圖所示:

這些特性里朽肥,HTTP/2,Auto HTTPS持钉,聽(tīng)著很熟衡招?這是我之前介紹的 Caddy 也有的特性,
因?yàn)?golang 實(shí)現(xiàn)這些太容易了每强。還有 Middleware 里的一大堆功能也差不多蚁吝。
我們?cè)谧鑫⒎?wù)的時(shí)候旱爆,這些通用的東西由 API Gateway 統(tǒng)一實(shí)現(xiàn)就好了,
如果你寫(xiě)的是個(gè)小的獨(dú)立應(yīng)用的后端窘茁,這些開(kāi)箱即用的功能倒是能提供很大的幫助怀伦。
其實(shí)今天我主要想說(shuō)說(shuō)最后一個(gè)特性里提到的,“中心化的 HTTP 錯(cuò)誤處理”山林。
RESTful API 錯(cuò)誤返回
一個(gè)團(tuán)隊(duì)?wèi)?yīng)當(dāng)有一份 RESTful API 的規(guī)范房待,而在規(guī)范中應(yīng)該規(guī)范響應(yīng)格式,包括所有錯(cuò)誤響應(yīng)的格式驼抹。
比如微軟的規(guī)范桑孩,
jsonapi.org 推薦規(guī)范等等。
大部分時(shí)候我們不需要實(shí)現(xiàn)的那么繁瑣框冀,我們規(guī)定一個(gè)簡(jiǎn)單的結(jié)構(gòu):
STATUS 400 Bad Request
{
"error": "InvalidID",
"message": "invalid id in your url query parameters"
}
傳統(tǒng)的錯(cuò)誤響應(yīng)可能只有一個(gè)伴隨 HTTP Status code 的 string 類(lèi)型的 message流椒,
如今我們把正常的響應(yīng)格式變成了 JSON ,那么把錯(cuò)誤返回也用 JSON 吧明也。
除了用 JSON 之外宣虾,我們又增加了一個(gè) error 字段,
這個(gè)字段是一個(gè)比 Status code 要詳細(xì)一個(gè)級(jí)別的 Key温数,
消費(fèi)端可以用這個(gè)約定的 Key 做更為靈活的錯(cuò)誤處理绣硝。
好了,我們就用這個(gè)簡(jiǎn)單的例子進(jìn)行下去撑刺,今天主題講的是 Echo 去統(tǒng)一處理的方法鹉胖。
Echo 怎么統(tǒng)一處理錯(cuò)誤?
其實(shí) Echo 的文檔雖然很漂亮够傍,但是不夠詳細(xì)甫菠,深入一點(diǎn)的內(nèi)容和例子并沒(méi)有。
但一個(gè)漂亮的 golang 項(xiàng)目冕屯,代碼即是文檔寂诱,我們應(yīng)該有去 godoc.org 查文檔的習(xí)慣。
我們找到 Echo 的 GoDoc愕撰,
看 Echo 類(lèi)型:
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, 順著找過(guò)去刹衫,
// HTTPErrorHandler is a centralized HTTP error handler.
type HTTPErrorHandler func(error, Context)
它是一個(gè)傳入 error 和 Context 并且沒(méi)有返回值的函數(shù)。
可是知道這些還是有點(diǎn)暈搞挣?并不知道怎么寫(xiě)這個(gè)函數(shù)啊带迟。
沒(méi)關(guān)系,我這篇文章就是講怎么寫(xiě)這個(gè)函數(shù)的囱桨。往下看吧仓犬。
定義錯(cuò)誤結(jié)構(gòu)
由于 golang 是靜態(tài)類(lèi)型,我們干啥都需要先定義個(gè)結(jié)構(gòu)舍肠,代碼如下:
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
}
這里我們做了三件事
- 定義了錯(cuò)誤的結(jié)構(gòu)搀继,其中包含 code窘面,key 和 message,key 和 message 可以被導(dǎo)出為 JSON叽躯。
- 做了個(gè)新建錯(cuò)誤結(jié)構(gòu)的函數(shù)财边,這樣就可以用一行代碼去新建一個(gè)錯(cuò)誤了。
- 給這個(gè)結(jié)構(gòu)增加了
Error
函數(shù)点骑,這樣這個(gè)結(jié)構(gòu)就成了一個(gè) golang 的 error 接口酣难。
處理錯(cuò)誤
我們終于可以寫(xiě)上文提到的自定義函數(shù)了,先看示例代碼我再做解釋黑滴,然后你就能寫(xiě)自己的了:
package main
import (
"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 ee, ok := err.(*echo.HTTPError); ok {
code = ee.Code
key = http.StatusText(code)
msg = key
} 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)
}
}
}
}
這個(gè)函數(shù)的功能就是根據(jù)傳進(jìn)來(lái)的 error 和上下文 Context憨募,組裝出合適的 HTTP 響應(yīng)。
可因?yàn)?golang 的 error 是一個(gè)接口袁辈,也就是第一個(gè)參數(shù)可能傳進(jìn)來(lái)任何奇怪的東西菜谣,
我們需要細(xì)心的處理一下。
第一部分我們定義了默認(rèn)值作為最壞的情況晚缩,在 HTTP API 里尾膊,消費(fèi)端要是看到這種最壞的情況,
說(shuō)明你要被扣獎(jiǎng)金了橡羞,除非你可以甩鍋給你依賴的模塊或基礎(chǔ)設(shè)施眯停。
第二部分我們先看看傳進(jìn)來(lái)的錯(cuò)誤是不是我們之前定義的济舆,如果是那就太好了卿泽。如果不是的話,
有可能是 Echo 返回的錯(cuò)誤滋觉,比如路由或者方法沒(méi)有找到之類(lèi)的签夭。如果還不是,
看來(lái)是一個(gè)其他的未知錯(cuò)誤椎侠,如果 Debug 開(kāi)著第租,那還好,不用扣獎(jiǎng)金我纪,我們把錯(cuò)誤明細(xì)直接返回
到 msg 里方便調(diào)試慎宾。如果也沒(méi)開(kāi) Debug ... 那只好硬著頭皮返回 500 并什么信息都不給了。
第三部分你可以基本照抄浅悉,是檢查上下文中是否聲明這個(gè)響應(yīng)已經(jīng)提交了趟据,只有沒(méi)提交的時(shí)候,
我們才需要把我們準(zhǔn)備好的錯(cuò)誤信息以 JSON 格式提交术健,順便打印錯(cuò)誤日志汹碱。另外,如果請(qǐng)求
是 HEAD 方法的話荞估,根據(jù)規(guī)范咳促,你只能返回狀態(tài) 204 并默默在日志記錄錯(cuò)誤了稚新。
應(yīng)用
好了,我們寫(xiě)好了統(tǒng)一的錯(cuò)誤處理跪腹,該怎么使用呢褂删? 來(lái)看一個(gè)極簡(jiǎn)的例子吧:
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)
}
這是個(gè)從 mongodb 取 user 的例子,
- 檢查url中的id是不是一個(gè)合法的id冲茸,不是的話笤妙,返回我們之前自定義的錯(cuò)誤。
- 去數(shù)據(jù)庫(kù)里查噪裕,如果沒(méi)有記錄蹲盘,返回 404 錯(cuò)誤。
- 如果查詢數(shù)據(jù)庫(kù)的操作出了其他錯(cuò)誤膳音,這個(gè)時(shí)候我們無(wú)能為力了召衔,只好直接把這個(gè)錯(cuò)誤返回。
- 一切正常沒(méi)錯(cuò)誤的話祭陷,我們返回狀態(tài) 200 和 JSON 數(shù)據(jù)苍凛。
我們可以看出,經(jīng)過(guò)這么一番折騰兵志,在寫(xiě)API的時(shí)候醇蝴,省心了很多。
我們可以隨手用一行代碼構(gòu)造錯(cuò)誤想罕,也可以直接把任何預(yù)測(cè)不到的錯(cuò)誤返回悠栓,
不用再麻煩的每次去構(gòu)造 500 錯(cuò)誤了。
怎么樣按价?快去安利小伙伴們用 echo 寫(xiě) HTTP API 吧惭适,真的很方便。