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

uuid · · 251 次点击 · · 开始浏览    
## 背景 在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统一响应&异常处理 | 艺术码农的小栈](https://itart.cn/blogs/2021/explore/go-http-response-uniform-handler.html)

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

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

251 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传