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}
}
相关文章:

go 微服务框架kratos错误处理的使用方法及原理探究
通过go语言原生http中响应错误的实现方法,逐步了解和使用微服务框架 kratos 的错误处理方式,以及探究其实现原理。 一、go原生http响应错误信息的处理方法 处理方法: ①定义返回错误信息的结构体 ErrorResponse // 定义http返回错误信息的…...

AI播客下载:Dwarkesh Podcast(关于AI的深度访谈)
Dwarkesh Podcast 是由 Dwarkesh Patel 主持的播客,专注于深度访谈和探讨各种复杂且有趣的话题。该播客在业界获得了极高的评价,被认为是对话和思想交流的平台。 Dwarkesh Podcast 的内容涵盖了多个领域,包括经济学、哲学以及科技等。例如&am…...

C++11function包装器的使用
类模板std::function是一种通用、多态的函数包装。std::function的实例可以对任何可以调用的目标实体进行存储、 复制和调用操作。这些目标实体包括普通函数、Lambda表达式、函数指针、以及其他函数对象等。std::function对象是对 C中现有的可调用实体的一种类型安全的包裹&…...

Vue3判断变量和对象不为null和undefined
Vue3判断变量和对象不为null和undefined 一、判断变量二、判断对象 一、判断变量 在 Vue 3 中,你可以使用 JavaScript 提供的常规方式来检查变量是否不为 null 和不为 undefined。你可以分别使用严格不等运算符 ! 来比较变量是否不为 null 和不为 undefined。以下是…...

C++进阶:C++11(列表初始化、右值引用与移动构造移动赋值、可变参数模版...Args、lambda表达式、function包装器)
C进阶:C11(列表初始化、右值引用与移动构造移动赋值、可变参数模版…Args、lambda表达式、function包装器) 今天接着进行语法方面知识点的讲解 文章目录 1.统一的列表初始化1.1{}初始化1.2 initializer_listpair的补充 2.声明相关关键字2.1a…...

Vue.js Promise 与 async/await 的比较
在现代 Web 开发中,异步操作是不可避免的。在处理异步数据获取时,开发人员通常会使用 Promise 或 async/await。虽然两者都可以实现相同的功能,但它们在代码风格、可读性和错误处理等方面有所不同。本文将对这两种方法进行比较,并…...

Qt 报错总结 No suitable kits found
目录 “No suitable kits found” 解决 解决方法参考: chatGPT辅助解决QT构建报错error: multiple target patterns 我的解决方法:把语言设置为空 “No suitable kits found” 解决 没有找到合适的kits套件,在安装Qt Creator时没有安装Min…...

ThingsBoard如何拆分前后端分离启动
后端启动 前端启动 注意事项 ThingsBoard是一个开源的物联网平台,它原本的设计就考虑到了现代Web应用的前后端分离架构。尽管其核心是一个后端服务,负责设备连接、数据处理和存储等,但其用户界面是作为单独的前端应用程序实现的,…...

加载页面 跳转 新页面 vue
通常,我们点页面上的详情,或者编辑,需要加载一个新的页面出来。 vue中加载页面的方法: 在父页面中(通常是某个模块目录下的index.vue),先写这行代码: import AddEditForm from ./…...

中国主要城市房价指数数据集(2011-2024)
数据来源:东方财富网 时间跨度:2011年1月 - 2024年4月 数据范围:中国主要城市 包含指标: 日期、城市 新建商品住宅价格指数-同比 新建商品住宅价格指数-环比 新建商品住宅价格指数-定基 二手住宅价格指数-环比 二手住宅价格指…...

Creating Server TCP listening socket *:6379: listen: Unknown error
错误: 解决方法: 在redis安装路径中打开cmd命令行窗口,输入 E:\Redis-x64-3.2.100>redis-server ./redis.windows.conf结果:...

JUnit5标记测试用例
使用场景: 通过Tag对用例分组: 环境分组:测试环境、预发布环境阶段分组:冒烟用例版本分组:V1.1、V1.2 Tag标记用例: 设置标签根据标签执行 结合Maven执行结合测试套件执行 设置标签: 通过T…...

在Windows10中重命名文件和文件夹的6种方法,有你熟悉和不熟悉的
序言 你可以通过多种方式在Windows 10上重命名文件。如果每次你想更改文件名时仍右键单击并选择“重命名”,那么我们有一些技巧可以加快更改速度。 使用文件资源管理器重命名文件和文件夹 Windows 10的文件资源管理器是一个功能强大的工具。你知道吗,有四种不同的方法可以…...

Go源码--sync库(1)sync.Once和
简介 这篇主要介绍 sync.Once、sync.WaitGroup和sync.Mutex sync.Once once 顾名思义 只执行一次 废话不说 我们看源码 英文介绍直接略过了 感兴趣的建议读一读 获益匪浅 其结构体如下 Once 是一个严格只执行一次的object type Once struct {// 建议看下源码的注解…...

头歌OpenGauss数据库-I.复杂查询第3关:统计总成绩
本关任务:计算每个班的语文总成绩和数学总成绩,要求科目中低于60分的成绩不记录总成绩。 tb_score结构数据: namechinesemathsA8998B9989C5566D8866E5566F8899tb_class表结构数据: stunameclassnameAC1BC2CC3DC2EC1FC3--#请在此添加实现代码 --# # # # # # # # # # Begin #…...

LeetCode hot100-47-N
105. 从前序与中序遍历序列构造二叉树给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。这题放选择题里还能选出来,前序中序一起确定了一颗什…...

中北大学软件学院计算机网络实验一
目录 1.实验名称2.实验目的3.实验内容4.实验过程(1)安装Packer Tracer并熟悉软件操作(2)利用一台型号为2960的交换机将2台pc机互连组建一个小型局域网(3)分别设置pc机的ip地址(4)验证…...

扩散模型学习1
DDPM 总体训练原理 https://www.bilibili.com/video/BV1nB4y1h7CN/?spm_id_from333.337.search-card.all.click&vd_sourcef745c116402814185ab0e8636c993d8f 讲得很好:每次都是输入t和noise-x的图像,预测noise之后得到和加入的noise比较;…...

【HTML】制作一个跟随鼠标的流畅线条引导页界面(可直接复制源码)
目录 前言 HTML部分 CSS部分 JS部分 效果图 总结 前言 无需多言,本文将详细介绍一段HTML代码,图中线条可跟随鼠标移动,具体内容如下: 开始 首先新建一个HTML的文本,文本名改为[index.html],创建好后右…...

vue3父子组件、跨级组件之间的通信之provide, inject -- 通俗易懂
当组件之间的跨度比较大时,用父子孙之间的通信需要层层传递,不优雅,也不方便传值和更新。 此方法适用于父子组件之间、爷孙组件之间的通信且高效。 父组件: 孙组件: 此处本组件触发点击事件后,count的数据…...

input输入多行文本,保存为.dot文件和对应的.txt文件
需求 不管是上面的dot还是这个dot 变成 input输入文本按“# ꧂ ꧁”结束保存在dot文本文件夹下,用txt保存每个文件文件名: 编号. 第二行有字文字 时间戳 代码 首先,我会创建一个Python脚本,它将接受用户的输入,直到…...

如何让社区版IDEA变得好用
如何让社区版IDEA变得好用 背景 收费版的idea功能非常强大,但是费用高。社区版的免费,但是功能被阉割了。如何才能让社区版Idea变得好用,就需要各种插件支持了。经过全局配置编码,maven,jdk版本,在加上各…...

Hsql每日一题 | day02
前言 就一直向前走吧,沿途的花终将绽放~ 题目:主播同时在线人数问题 如下为某直播平台主播开播及关播时间,根据该数据计算出平台最高峰同时在线的主播人数。 id stt edt 1001,2021-06-14 12:12:12,2021-06-14 18:1…...

RepOptimizer原理与代码解析(ICLR 2023)
paper:Re-parameterizing Your Optimizers rather than Architectures offcial implementation:https://github.com/dingxiaoh/repoptimizers 背景 神经网络的结构设计是将先验知识融入模型中。例如将特征转换建模成残差相加的形式(\(yf(x…...

持续总结中!2024年面试必问 20 道 Redis面试题(六)
上一篇地址:持续总结中!2024年面试必问 20 道 Redis面试题(五)-CSDN博客 十一、Redis集群的原理是什么? 集群是一种分布式系统架构,它由多个节点组成,这些节点共同工作以提供高可用性、扩展性…...

【通义千问—Qwen-Agent系列2】案例分析(图像理解图文生成Agent||多模态助手|| 基于ReAct范式的数据分析Agent)
目录 前言一、快速开始1-1、介绍1-2、安装1-3、开发你自己的Agent 二、基于Qwen-Agent的案例分析2-0、环境安装2-1、图像理解&文本生成Agent2-2、 基于ReAct范式的数据分析Agent2-3、 多模态助手 附录1、agent源码2、router源码 总结 前言 Qwen-Agent是一个开发框架。开发…...

10G SFP双口万兆以太网控制器,高速光口网络接口卡
2-Port 10G SFP NIC 是一款高速网 络接口卡,采用了 PCI Express 3.0 x8 接口,支持双 端口万兆以太网,具有高性能、高可靠性、低功耗等 优点,是数据中心、云计算、虚拟化等领域的理想选 择。 支持多种网络协议,如 …...

[前端|vue] 验证器validator使用笔记 (笔记)
文档 validator.js文档地址 规则编写示例 element-plus 使用示例 const captchaLoginRules {phoneNumber: [{ required: true, message: 手机号不能为空, trigger: blur },{validator: (_rule: any, value: string, _callback: any): boolean > {return isMobilePhone(…...

欢乐钓鱼大师攻略大全,游戏自动辅助,钓鱼大全!
欢迎来到《欢乐钓鱼大师》的攻略大全!本文将为你详细介绍游戏中的各类玩法、技巧和注意事项,帮助你快速掌握游戏精髓,成为一名真正的钓鱼大师。攻略内容包括新手鱼竿选择、锦标赛攻略、实用技巧、藏宝图玩法、箱子开法等多个方面。让我们一起…...

Prompt - 流行的10个框架
转载自:https://juejin.cn/post/7287412759050289212 文章目录 1、ICIO框架2、CRISPE框架3、BROKE框架4、CREATE框架5、TAG框架6、RTF框架7、ROSES框架8、APE框架9、RACE框架10、TRACE框架 测试用例 为了看到不同的Prompt框架效果,本文定义一个统一的测…...