背景
在web开发中,一般会写如下方法, 处理http的请求和响应结果:
// 处理hello请求的handler
func HelloHandler(w http.ResponseWriter, req *http.Request) {
name := req.URL.Query().Get("name")
if name == "" { // name 必填判断
w.Write([]byte("name is required"))
w.WriteHeader(http.StatusBadRequest)
return
}
data, err := xxService.Find(name)
if err !=nil{ // 异常响应
w.Write([]byte(err))
w.WriteHeader(http.StatusInternalServerError)
return
}
b, err := json.Marshal(data)
if err != nil{ // 反序列化异常
w.Write([]byte(err))
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write(b) // 响应结果
w.WriteHeader(http.StatusOK)
}
func main() {
// 注册路由
http.HandleFunc("/hello", HelloHandler)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
问题1:职责过重
handler方法中既要处理参数接收、service的调用,还要处理异常和封装响应的结果。
问题2:代码重复
同一个handler会有多个err需要重复处理,通常也只是简单打印结果和将异常响应给客户端,所以代码类似容易出现重复处理,例如:name为空、反序列化异常、调用service可能出现异常都只是打印
问题3:无法统一异常处理
每个err都是单独处理,会散落在handler中的不同位置,无法做到统一的异常处理,如果需要调整异常的处理逻辑,例如:打印异常的格式、异常堆栈等, 需要大量调整代码。
问题4:无法统一响应处理
在开发API时,我们一般会统一响应格式,例如:
type response struct {
Code int
Message string
Error string
Data interface{}
}
- Code:编码,20000表示成功、500xxx表示异常等
- Message:提示信息
- Error:异常信息
- Data:正常响应数据
如果不能统一处理就需要重复在每个handler中创建该结构体,例如:
func HelloHandler(w http.ResponseWriter, req *http.Request) {
...
repo := response{
Code: 200000,
Message: "",
Error: "",
Data: nil,
}
err := json.NewEncoder(w).Encode(repo)
if err !=nil{
log.Error(err)
return
}
w.WriteHeader(http.StatusOK)
}
优化方案
将异常处理和响应的逻辑从handler中剥离出来,创建一个统一处理的中间件。
步骤:
首先,调整handler方法的返回值,由原来的无返回结果,改为返回data和异常error,当handler中有遇到异常就直接返回,结果也是直接返回,不再处理。
func HelloHandler(w http.ResponseWriter, req *http.Request) (data interface{}, err error) {
name := req.URL.Query().Get("name")
if name == "" {
return nil, errors.New("name is required")
}
return xxService.Find(name)
}
调整完后的handler的代码量就会简化很多,也更加清晰。
其次,创建中间件,统一处理异常和响应:
type handler func(w http.ResponseWriter, req *http.Request) (data interface{}, err error)
// 统一处理异常,适配http.HandlerFunc
func responseHandler(h handler) http.HandlerFunc {
type response struct {
Code int
Message string
Data interface{}
}
return func(w http.ResponseWriter, req *http.Request) {
data, err := h(w, req) // 调用handler方法
if err != nil { // 异常处理
log.Error(err)
w.Write([]byte(err.Error()))
w.WriteHeader(http.StatusInternalServerError)
return
}
resp := response{
Code: 2000000,
Message: "success",
Data: data,
}
// 响应结果处理
err = json.NewEncoder(w).Encode(resp)
if err != nil {
log.Error(err)
w.Write([]byte(err.Error()))
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
}
最后,调整路由注册,原来直接使用handler,现在需要包裹一层responseHandler,将异常&响应结果的处理逻辑委托给responseHandler。
func main() {
http.HandleFunc("/hello", responseHandler(HelloHandler)) // 将改造后的HelloHandler增加一层responseHandler
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
这样就完成了优化
小结
优化后效果:
- 职责单一:改造后的handler将异常和响应的逻辑剥离出来,职责更加单一。
- 减少重复代码。 消除了重复处理err和响应的代码,更加简洁。
- 统一异常&结果处理。responseHandler可以对error和响应格式统一处理,如果后续需要额外增加异常处理逻辑或是调整响应格式,只需要修改responseHandler,无需调整其他代码。
方案同样。也适用其他的框架,例如:Gin
func main() {
r := gin.Default()
r.GET("/hello", responseHandler(HelloHandler))
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
// 处理hello请求的handler。如果有异常返回,响应结果也是直接放回
func HelloHandler(ctx *gin.Context) (data interface{}, err error) {
name := ctx.Query("name")
if name == "" {
return nil, errors.New("name is required")
}
return xxService.Find(name)
}
type handler func(ctx *gin.Context) (data interface{}, err error)
// 中间件:处理异常和封装响应结果,同时适配gin.HandlerFunc
func response1Handler(h handler) gin.HandlerFunc {
type response struct {
Code int
Message string
Data interface{}
}
return func(ctx *gin.Context) {
data, err := h(ctx)
if err != nil {
log.Error(err)
ctx.Error(err)
return
}
resp := response{
Code: 2000000,
Message: "success",
Data: data,
}
ctx.JSON(http.StatusOK, resp)
})
}
我的博客: Go http handler统一响应&异常处理 | 艺术码农的小栈
|