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.mzph.cn/diannao/14089.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无人机飞手前途分析

无人机飞手的前途充满了各种可能性和挑战,这主要得益于无人机技术的快速发展和广泛应用。以下是对无人机飞手前途的一些分析: 1. 技术发展与需求增长:随着无人机技术的不断进步,其应用场景也在持续扩大。从地理测绘、巡检、农林植…

利用阿里OSS服务给文件设置过期删除--简单版

在云存储广泛应用的今天,阿里云的Object Storage Service(OSS)以其高度可扩展性、安全性和成本效益,成为了众多企业和开发者存储海量数据的首选方案。随着数据量的不断膨胀,高效的数据管理和成本控制变得尤为重要。其中…

IT学习笔记--Kafka

Kafka概述: 定义: Kafka是一个分布式的基于发布/订阅模式的消息队列,主要应用于大数据实时处理领域。 消息队列消息队列的两种模式: 点对点模式: 消息生产者生产消息发送到Queue中,然后消息消费者从Queue中取出并且消费消息。 消息被消费以后&#…

Linux中解决普通用户使用不了sudo问题

目录 sudo的使用场景sudo使用不了的原因解决方法 sudo的使用场景 之前我们介绍了文件的权限问题 如果一个普通用户想去执行一个它命令之外的权限,只能使用sudo 比如普通用户使用yum去安装软件,需要sudo yum xxxx sudo使用不了的原因 这里我们用普通用户…

小恐龙跳一跳源码

小恐龙跳一跳源码是前两年就火爆过一次的小游戏源码,不知怎么了今年有火爆了,所以今天就吧这个源码分享出来了!有喜欢的直接下载就行,可以本地单机直接点击index.html进行运行,又或者放在虚拟机或者服务器上与朋友进行…

python 获取视频的时长

以下是几种获取视频时长的实现方法: 方法一:使用moviepy库 from moviepy.editor import VideoFileClipdef get_video_duration(file_path):video VideoFileClip(file_path)duration video.durationvideo.close()return duration 方法二:…

SAP-FICO-凭证编号控制

成本凭证编号KANK 如果自己的公司下没有,直接复制系统原有的就可以。使用系统默认即可。 如果不维护 会报错“CO-凭证编号分配对于成本控制范围****中的商业事务COIN无效” 财务凭证编号FBN1 可以用OBH2批量复制编号范围。 物料账期MMPV 财务账期OB52

python使用base加密解密

原理 base编码是一种加密解密措施,目前常用的有base16、base32和base64。其大致原理比较简单。 以base64为例,base64加密后共有64中字符。其加密过程是编码后将每3个字节作为一组,这样每组就有3*824位。将每6位作为一个单位进行编码&#xf…

1个逗号,提升Python代码质量

有些时候,我们会在Python代码中看到列表或其他科迭代对象的结尾会存在一个逗号: 而且编辑器和解释器都容许这种逗号的存在,它就叫作拖尾逗号。 通常是为了在频繁地增减数组元素的时候同时保证语法的正确,且拖尾逗号不占用数组的长…

MySQL 主备环境搭建 docker

MySQL 主备环境搭建 docker 拉取docker镜像 sudo docker pull mysql:8.0 启动容器 docker run -p 3339:3306 --name mysql-master -e MYSQL_ROOT_PASSWORD123456 -d mysql:8.0docker run -p 3340:3306 --name mysql-slave -e MYSQL_ROOT_PASSWORD123456 -d mysql:8.0配置 M…

第四十二天 | 背包问题理论

二维: 1.dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。 2.递归公式: dp[i][j] max(dp[i - 1][j], dp[i - 1][j - weight[i]] value[i]); 3.初始化: 首先从dp[i][j]的定义出发…

基于xilinx fpga RFSOC系列的Ultrascale+ RF Data Converter ip详解说明

目录 1 概述2 IP功能2.1 ADC性能2.2 DAC性能3 IP端口4 代码框架4.1 ADC功能框图4.2 DAC功能框图5 收发数据时序5.1 ADC数据格式5.2 DAC数据格式6 时钟配置6.1 ADC/DAC参考时钟7 数据格式配置模式7.1 ADC的配置模式7.1.1 Real -> real;7.1.2 Real ->IQ;7.1.3 IQ -> IQ;…

【设计模式】JAVA Design Patterns——Bridge(桥接模式)

🔍目的 将抽象与其实现分离,以便二者可以独立变化。 🔍解释 真实世界例子 考虑一下你拥有一种具有不同附魔的武器,并且应该允许将具有不同附魔的不同武器混合使用。 你会怎么做? 为每个附魔创建每种武器的多个副本&…

当代人工智能三教父——深度学习三巨头

文章目录 引言 人物介绍 突出贡献 专业名词解释 引言 今天下午闲来无事翻阅了一下csdn首页的头条文章——《27 岁天才创始人 Joel Hellermark 分享了自己和“AI 教父” Geoffery Hinton 的最新采访》 感觉挺有意思,就从头到尾的看了一遍,里面有很多…

pyqt5与yolov5进行视频检测(一)——登录操作

项目效果展示 一、登录界面 二、主界面 目前在更新中。。。 一、设计 二、登录代码 注意:下面会导入主界面的包,图片资源自己设计一下,密码保存时没设计加密,需要自行设计 main_window主界面下文会设计from main_window impor…

无线通信的穿墙能力主要取决于哪些指标

无线通信的穿墙能力是指无线信号在穿越建筑物墙壁时,其信号衰减程度以及能否维持足够强度以进行稳定通信的能力。穿墙能力的好坏直接影响到无线通信在室内环境中的覆盖范围和使用体验。 一、无线信号的频率 无线信号的频率是影响穿墙能力的重要因素之一。一般来说…

工行音视频服务平台建设与应用经验

近些年来,伴随着技术能力的积累突破,音视频服务开始蓬勃生长走进千家万户,使用远程视频通话、观看各类视频直播逐渐成为人们的日常,而金融服务作为社会生活的重要组成部分,自然需要积极拥抱应用新技术。 如今&#xff…

怎么知道Python包的依赖项

要查看Python包的依赖项,有几种方法可以做到这一点: 使用pip: pip是Python的包管理器,它允许你安装和管理Python库。要查看一个包的依赖关系,你可以使用pip show命令加上包名,但请注意,直接用pip show并不直接列出依赖项,它提供包的详细信息,包括它的安装路径。为了查看…

Kubernetes Deployment 之扩缩容与滚动更新

Kubernetes Deployment 之扩缩容与滚动更新 Deployment 扩缩容 扩缩容非常简单,我们可以直接调整 replica 副本数目,然后 kubectl apply指定进行动态更新。下面将nginx-deployment动态改为 1 个 Pod 和 3 个 Pod 的操作 apiVersion: apps/v1 kind: De…

20232820 2023-2024-2 《网络攻防实践》实践十一报告

20232820 2023-2024-2 《网络攻防实践》实践十一报告 1.实践内容 web浏览器渗透攻击 任务:使用攻击机和Windows靶机进行浏览器渗透攻击实验,体验网页木马构造及实施浏览器攻击的实际过程 取证分析实践—网页木马攻击场景分析 攻防对抗实践—web浏览…