借助移动端的增加,现在 RESTful 风格的 API 已经十分流行,
用各类语言去写后端 API 都有很成熟方便的方案,用 golang 写后端 API 更是生产力的表明,
你能够用不输 python/ruby 这类动态语言的速度,写出性能高出一两个数量级的后端 API 。python
因为 golang 的标准库在网络方面已经很完善,致使框架发挥余地不大。不少高手都说,
用什么框架,用标准库就写好了,框架只是语法糖而已,还会限制项目的发展。
不过咱们并非高手,语法糖也是糖,用一个趁手的框架仍是能提升很多效率的。
要是在半年前,你让我推荐框架,我会说有不少,都各有优缺点,除了 beego 随便选一个就能够。
可是来到2017年,一个叫 Echo
的框架脱颖而出。这是我目前最推荐的框架。
Echo 的宣传语用的是 “高性能,易扩展,极简 Go Web 框架” 。它的一些特性以下图所示:git
这些特性里,HTTP/2,Auto HTTPS,听着很熟?这是我以前介绍的 Caddy 也有的特性,
由于 golang 实现这些太容易了。还有 Middleware 里的一大堆功能也差很少。
咱们在作微服务的时候,这些通用的东西由 API Gateway 统一实现就行了,
若是你写的是个小的独立应用的后端,这些开箱即用的功能却是能提供很大的帮助。github
其实今天我主要想说说最后一个特性里提到的,“中心化的 HTTP 错误处理”。golang
一个团队应当有一份 RESTful API 的规范,而在规范中应该规范响应格式,包括全部错误响应的格式。
好比微软的规范,
jsonapi.org 推荐规范等等。
大部分时候咱们不须要实现的那么繁琐,咱们规定一个简单的结构:mongodb
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 去统一处理的方法。json
其实 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, 顺着找过去,api
// HTTPErrorHandler is a centralized HTTP error handler.
type HTTPErrorHandler func(error, Context)复制代码
它是一个传入 error 和 Context 而且没有返回值的函数。
但是知道这些仍是有点晕?并不知道怎么写这个函数啊。
不要紧,我这篇文章就是讲怎么写这个函数的。往下看吧。ruby
因为 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
}复制代码
这里咱们作了三件事
Error
函数,这样这个结构就成了一个 golang 的 error 接口。咱们终于能够写上文提到的自定义函数了,先看示例代码我再作解释,而后你就能写本身的了:
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 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 的例子,
咱们能够看出,通过这么一番折腾,在写API的时候,省心了不少。
咱们能够随手用一行代码构造错误,也能够直接把任何预测不到的错误返回,
不用再麻烦的每次去构造 500 错误了。
怎么样?快去安利小伙伴们用 echo 写 HTTP API 吧,真的很方便。