当前位置: 首页 > news >正文

go 微服务框架kratos错误处理的使用方法及原理探究

 通过go语言原生http中响应错误的实现方法,逐步了解和使用微服务框架 kratos 的错误处理方式,以及探究其实现原理。

一、go原生http响应错误信息的处理方法

  • 处理方法:

①定义返回错误信息的结构体 ErrorResponse

// 定义http返回错误信息的结构体
type ErrorResponse struct {Code    int    `json:"code"`Message string `json:"message"`
}

②根据业务逻辑,为结构体赋值相应的错误信息

//这里为了简化函数,不进行业务逻辑判断,而直接返回错误信息
er := &ErrorResponse{Code:    403,Message: "用户名不能为空",
}

③将错误信息序列化,并写入到 http.ResponseWriter 中

// 设置响应头为JSON类型
w.Header().Set("Content-Type", "application/json")// 设置响应状态码为400
w.WriteHeader(http.StatusBadRequest)// 将ErrorResponse转换为JSON并写入响应体
//json.NewEncoder(w).Encode(he)//将错误信息结构体序列化,并返回
res, _ := json.Marshal(er)
w.Write(res)

  • 代码示例:
package mainimport ("encoding/json""net/http"
)// 定义http返回错误信息的结构体
type ErrorResponse struct {Code    int    `json:"code"`Message string `json:"message"`
}func Login(w http.ResponseWriter, r *http.Request) {//这里为了简化函数,不进行业务逻辑判断,而直接返回错误信息er := &ErrorResponse{Code:    403,Message: "用户名不能为空",}// 设置响应头为JSON类型w.Header().Set("Content-Type", "application/json")// 设置响应状态码为400w.WriteHeader(http.StatusBadRequest)// 将ErrorResponse转换为JSON并写入响应体//json.NewEncoder(w).Encode(he)//将错误信息结构体序列化,并返回res, _ := json.Marshal(er)w.Write(res)
}func main() {//创建一个 HTTP 请求路由器mux := http.NewServeMux()mux.Handle("/login", http.HandlerFunc(Login))http.ListenAndServe(":8081", mux)
}
  • 效果演示:

二、微服务框架kratos响应错误的方式

Kratos官网有关错误处理的介绍:错误处理 | Kratos

Kratos 有关错误处理的 examples 代码见:examples/errors 、examples/http/errors

1、kratos默认的错误信息格式
  • kratos响应错误信息的默认JSON格式为:
{// 错误码,跟 http-status 一致,并且在 grpc 中可以转换成 grpc-status"code": 500,// 错误原因,定义为业务判定错误码"reason": "USER_NOT_FOUND",// 错误信息,为用户可读的信息,可作为用户提示内容"message": "invalid argument error",// 错误元信息,为错误添加附加可扩展信息"metadata": {"foo": "bar"}
}
  • 使用方法:

①导入 kratos 的 errors 包

import "github.com/go-kratos/kratos/v2/errors"

②在业务逻辑中需要响应错误时,用 New 方法生成错误信息(或通过 proto 生成的代码响应错误)

注意:这里的 New 方法是 Kratos 框架中的 errros.New,而不是 go 原生的 errors.New

func NewLoginRequest(username, password string) (*LoginRequest, error) {// 校验参数if username == "" {//通过 New 方法创建一个错误信息err := errors.New(500, "USER_NAME_EMPTY", "用户名不能为空")// 传递metadataerr = err.WithMetadata(map[string]string{ "remark": "请求参数中的 phone 字段为空",})return nil, err}if password == "" {// 通过 proto 生成的代码响应错误,并且包名应替换为自己生成代码后的 package name      return nil, api.ErrorPasswordIsEmpty("密码不能为空")}return &LoginRequest{username: username,password: password,}, nil
}
  • 返回结果:

2、自定义错误信息格式

如果不想使用 kratos 默认的错误响应格式,可以自定义错误信息处理格式,方法如下:

①自定义错误信息结构体

type HTTPError struct {Code     int    `json:"code"`Message  string `json:"message"`MoreInfo string `json:"moreInfo"`
}

②实现 FromError、errorEncoder 等方法

func (e *HTTPError) Error() string {return fmt.Sprintf("HTTPError code: %d message: %s", e.Code, e.Message)
}// FromError try to convert an error to *HTTPError.
func FromError(err error) *HTTPError {if err == nil {return nil}if se := new(HTTPError); errors.As(err, &se) {return se}return &HTTPError{Code: 500}
}func errorEncoder(w stdhttp.ResponseWriter, r *stdhttp.Request, err error) {se := FromError(err)codec, _ := http.CodecForRequest(r, "Accept")body, err := codec.Marshal(se)if err != nil {w.WriteHeader(500)return}w.Header().Set("Content-Type", "application/"+codec.Name())w.WriteHeader(se.Code)_, _ = w.Write(body)
}

③创建 http.Server 时,使用函数 http.ErrorEncoder() 将上述 errorEncoder 添加到 ServerOption 中

httpSrv := http.NewServer(http.Address(":8000"),http.ErrorEncoder(errorEncoder),
)

④业务逻辑中需要响应错误的地方返回自定义消息对象

return &HTTPError{Code: 400, Message: "用户名不存在", MoreInfo: "请求参数中 userName = 张三"}
  • 完整代码示例为:
package mainimport ("errors""fmt""log"stdhttp "net/http""github.com/go-kratos/kratos/v2""github.com/go-kratos/kratos/v2/transport/http"
)// HTTPError is an HTTP error.
type HTTPError struct {Code     int    `json:"code"`Message  string `json:"message"`MoreInfo string `json:"moreInfo"`
}func (e *HTTPError) Error() string {return fmt.Sprintf("HTTPError code: %d message: %s", e.Code, e.Message)
}// FromError try to convert an error to *HTTPError.
func FromError(err error) *HTTPError {if err == nil {return nil}if se := new(HTTPError); errors.As(err, &se) {return se}return &HTTPError{Code: 500}
}func errorEncoder(w stdhttp.ResponseWriter, r *stdhttp.Request, err error) {se := FromError(err)codec, _ := http.CodecForRequest(r, "Accept")body, err := codec.Marshal(se)if err != nil {w.WriteHeader(500)return}w.Header().Set("Content-Type", "application/"+codec.Name())w.WriteHeader(se.Code)_, _ = w.Write(body)
}func main() {httpSrv := http.NewServer(http.Address(":8082"),http.ErrorEncoder(errorEncoder),)router := httpSrv.Route("/")router.GET("login", func(ctx http.Context) error {return &HTTPError{Code: 400, Message: "用户名不存在", MoreInfo: "请求参数中 userName = 张三"}})app := kratos.New(kratos.Name("mux"),kratos.Server(httpSrv,),)if err := app.Run(); err != nil {log.Fatal(err)}
}
  • 返回结果:

3、kratos返回错误信息JSON的源码探究

至此,了解了 go 原生 http 和微服务框架 kratos 响应错误信息的处理方式,对比可发现:

①在原生http响应处理中,我们先将错误消息结构体序列化 res, _ := json.Marshal(er),然后通过 http.ResponseWriter.Write(res) 写入错误信息JSON并返回

②在 kratos 中,我们在业务处理函数中仅仅通过 return errors.New() 返回了一个 error,并没有将其序列化,但整个http请求却返回了一个有关错误信息的 json 字符串

是什么原因呢?原来是 kratos 框架内部完成了将错误信息结构体序列化并写入http.ResponseWriter的过程。

具体实现方式如下:

  • http server 结构体 Server 中含有一个字段 ene EncodeErrorFunc,专门用来进行错误处理
//http/server.go
// Server is an HTTP server wrapper.
type Server struct {*http.Serverlis         net.ListenertlsConf     *tls.Configendpoint    *url.URLerr         errornetwork     stringaddress     stringtimeout     time.Durationfilters     []FilterFuncmiddleware  matcher.MatcherdecVars     DecodeRequestFuncdecQuery    DecodeRequestFuncdecBody     DecodeRequestFuncenc         EncodeResponseFuncene         EncodeErrorFunc           // 用于错误处理strictSlash boolrouter      *mux.Router
}//http/codec.go
// EncodeErrorFunc is encode error func.
type EncodeErrorFunc func(http.ResponseWriter, *http.Request, error)
  • 使用 NewServer() 创建时 http Server 时,ene 属性会默认为 DefaultErrorEncoder,该函数会将序列化错误信息,并写入到 http.ResponseWriter 中
//http/server.go
// NewServer creates an HTTP server by options.
func NewServer(opts ...ServerOption) *Server {srv := &Server{network:     "tcp",address:     ":0",timeout:     1 * time.Second,middleware:  matcher.New(),decVars:     DefaultRequestVars,decQuery:    DefaultRequestQuery,decBody:     DefaultRequestDecoder,enc:         DefaultResponseEncoder,ene:         DefaultErrorEncoder,     //默认的错误处理函数strictSlash: true,router:      mux.NewRouter(),}for _, o := range opts {o(srv)}srv.router.StrictSlash(srv.strictSlash)srv.router.NotFoundHandler = http.DefaultServeMuxsrv.router.MethodNotAllowedHandler = http.DefaultServeMuxsrv.router.Use(srv.filter())srv.Server = &http.Server{Handler:   FilterChain(srv.filters...)(srv.router),TLSConfig: srv.tlsConf,}return srv
}//http/codec.go
// DefaultErrorEncoder encodes the error to the HTTP response.
func DefaultErrorEncoder(w http.ResponseWriter, r *http.Request, err error) {se := errors.FromError(err)codec, _ := CodecForRequest(r, "Accept")body, err := codec.Marshal(se)    //序列化错误信息结构体if err != nil {w.WriteHeader(http.StatusInternalServerError)return}w.Header().Set("Content-Type", httputil.ContentType(codec.Name()))w.WriteHeader(int(se.Code))_, _ = w.Write(body)            //将序列化的结果写入到响应中
}
  • 路由处理函数 router.GET() 等会调用 Router.Handle, 其中会判断 HandlerFunc 是否有返回错误,如果有,则会调用 server.ene 函数,从而完成错误信息序列化并返回
//main.go
func main() {httpSrv := http.NewServer(http.Address(":8082"),)router := httpSrv.Route("/")router.GET("login", func(ctx http.Context) error {return errors.New(500, "USER_NOT_FOUND", "用户名不存在")})
}//http/router.go
// GET registers a new GET route for a path with matching handler in the router.
func (r *Router) GET(path string, h HandlerFunc, m ...FilterFunc) {r.Handle(http.MethodGet, path, h, m...)
}//http/router.go
// Handle registers a new route with a matcher for the URL path and method.
func (r *Router) Handle(method, relativePath string, h HandlerFunc, filters ...FilterFunc) {next := http.Handler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {ctx := r.pool.Get().(Context)ctx.Reset(res, req)//重点:这里判断路由处理函数是否返回了 error,如果是,则调用 server.ene 函数,序列化错误信息并返回if err := h(ctx); err != nil {r.srv.ene(res, req, err)}ctx.Reset(nil, nil)r.pool.Put(ctx)}))next = FilterChain(filters...)(next)next = FilterChain(r.filters...)(next)r.srv.router.Handle(path.Join(r.prefix, relativePath), next).Methods(method)
}
  • 自定义错误消息结构体后,创建 http server 时,通过 http.ErrorEncoder(errorEncoder) 将自定义的错误处理函数赋值给 server.ene,替换了默认的 DefaultErrorEncoder
// main.go
//自定义的错误处理函数
func errorEncoder(w stdhttp.ResponseWriter, r *stdhttp.Request, err error) {se := FromError(err)codec, _ := http.CodecForRequest(r, "Accept")body, err := codec.Marshal(se)if err != nil {w.WriteHeader(500)return}w.Header().Set("Content-Type", "application/"+codec.Name())w.WriteHeader(se.Code)_, _ = w.Write(body)
}// main.go
func main() {httpSrv := http.NewServer(http.Address(":8082"),http.ErrorEncoder(errorEncoder),  // 将自定义的错误处理函数赋值给 sever)
}// http/server.go
// ErrorEncoder with error encoder.
func ErrorEncoder(en EncodeErrorFunc) ServerOption {return func(o *Server) {o.ene = en}
}

http://www.lryc.cn/news/352046.html

相关文章:

  • AI播客下载:Dwarkesh Podcast(关于AI的深度访谈)
  • C++11function包装器的使用
  • Vue3判断变量和对象不为null和undefined
  • C++进阶:C++11(列表初始化、右值引用与移动构造移动赋值、可变参数模版...Args、lambda表达式、function包装器)
  • Vue.js Promise 与 async/await 的比较
  • Qt 报错总结 No suitable kits found
  • ThingsBoard如何拆分前后端分离启动
  • 加载页面 跳转 新页面 vue
  • 中国主要城市房价指数数据集(2011-2024)
  • Creating Server TCP listening socket *:6379: listen: Unknown error
  • JUnit5标记测试用例
  • 在Windows10中重命名文件和文件夹的6种方法,有你熟悉和不熟悉的
  • Go源码--sync库(1)sync.Once和
  • 头歌OpenGauss数据库-I.复杂查询第3关:统计总成绩
  • LeetCode hot100-47-N
  • 中北大学软件学院计算机网络实验一
  • 扩散模型学习1
  • 【HTML】制作一个跟随鼠标的流畅线条引导页界面(可直接复制源码)
  • vue3父子组件、跨级组件之间的通信之provide, inject -- 通俗易懂
  • input输入多行文本,保存为.dot文件和对应的.txt文件
  • 如何让社区版IDEA变得好用
  • Hsql每日一题 | day02
  • RepOptimizer原理与代码解析(ICLR 2023)
  • 持续总结中!2024年面试必问 20 道 Redis面试题(六)
  • 【通义千问—Qwen-Agent系列2】案例分析(图像理解图文生成Agent||多模态助手|| 基于ReAct范式的数据分析Agent)
  • 10G SFP双口万兆以太网控制器,高速光口网络接口卡
  • [前端|vue] 验证器validator使用笔记 (笔记)
  • 欢乐钓鱼大师攻略大全,游戏自动辅助,钓鱼大全!
  • Prompt - 流行的10个框架
  • PYQT5点击Button执行多次问题解决方案(亲测)