1

Go http handler统一响应&异常处理

 2 years ago
source link: https://studygolang.com/articles/35335
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Go http handler统一响应&异常处理

uuid · 3天之前 · 183 次点击 · 预计阅读时间 5 分钟 · 大约8小时之前 开始浏览    

在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统一响应&异常处理 | 艺术码农的小栈


有疑问加站长微信联系(非本文作者))

280

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:701969077


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK