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

news2024/11/23 20:11:48

 通过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 main

import (
	"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")

	// 设置响应状态码为400
	w.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", "用户名不能为空")
        // 传递metadata
		err = 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 main

import (
	"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.Server
	lis         net.Listener
	tlsConf     *tls.Config
	endpoint    *url.URL
	err         error
	network     string
	address     string
	timeout     time.Duration
	filters     []FilterFunc
	middleware  matcher.Matcher
	decVars     DecodeRequestFunc
	decQuery    DecodeRequestFunc
	decBody     DecodeRequestFunc
	enc         EncodeResponseFunc
	ene         EncodeErrorFunc           // 用于错误处理
	strictSlash bool
	router      *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.DefaultServeMux
	srv.router.MethodNotAllowedHandler = http.DefaultServeMux
	srv.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.coloradmin.cn/o/1686213.html

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

相关文章

vscode 插件开发指南

1安装nodejs、vscode 2安装插件脚手架 npm install -g yo generator-code 3使用命令创建插件项目 yo code 4在vscode中打开项目 5运行调试,按F5键 6在新打开的窗口中按shiftctrlp 然后执行命令 7配置右键菜单命令 遇到问题: 1.package.json中vsc…

【ELK日志收集过程】

文章目录 为什么要使用ELK收集日志ELK具体应用场景ELK日志收集的流程 为什么要使用ELK收集日志 使用 ELK(Elasticsearch, Logstash, Kibana)进行日志收集和分析有多种原因。ELK 堆栈提供了强大、灵活且可扩展的工具集,能够满足现代 IT 系统对…

B端概念稿,贼靓!像概念车一样未必落地,但是潮流引领。

概念稿在UI设计中往往难以落地, 主要有以下几个原因: 抽象性:概念稿通常是设计师在初始阶段为了表达和传达设计理念而创建的,它们往往比较抽象和概念化。这使得概念稿在实际落地时需要进一步细化和具体化,以便开发人员…

ChatGPT类大模型应用入门了解与使用

一 前言 ChatGPT大众热情逐渐褪去,但在后台技术人的探索还处于热火朝天状态。如果我们生活的世界是一杯清水, 那类似ChatGPT的语言大模型技术的横空出世就如滴入水杯的一滴墨汁,第一滴很显眼,但实际上是后续墨汁慢慢扩散渗透才是…

[数据集][目标检测]森林火灾检测数据集VOC+YOLO格式362张1类别

数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):362 标注数量(xml文件个数):362 标注数量(txt文件个数):362 标注类别…

生成模型 | 从 VAE 到 Diffusion Model (上)

文章目录 一,GAN(对抗式生成网络)二,Auto-Encoder(AE) 和 Denoising Auto-Encoder (DAE)三,VAE四,VQ-VAE (Vector Quantized Variational Autoencoder)VQ-VAE 2小总结: 五,DALL-E (O…

Google speech command 数据集获取

🏆本文收录于「Bug调优」专栏,主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&…

Java学习【接口的使用实例,浅克隆与深克隆】

Java学习【接口的使用实例&#xff0c;浅拷贝与深拷贝】 Comparable接口String类型比较多个对象的比较 Comparator接口Cloneable接口浅拷贝深拷贝 Comparable接口 当我们想要比较两个基本数据类型的大小时直接用 > , < , 就可以了&#xff0c;那么如果是自定义的类要根…

为什么我们应该放弃定义敏感数据?

个人数据与人以及其他个人数据深深地交织在一起&#xff0c;它就像一幅巨大的挂毯&#xff0c;而这些线是无法轻易拆开的。尝试定义敏感数据就像徒劳地试图从挂毯中找出不同的线头一样&#xff0c;线头与其他线头交织在一起&#xff0c;一旦开始拆线&#xff0c;整个挂毯就会散…

IP地址概述和配置

一.IP地址的概述 在计算机网络中&#xff0c;连接的网络设备和计算机都有唯一的地址&#xff0c;以此作为该计算机在internet中的唯一标识。 二.IP地址的定义 IP地址&#xff08;internet protocol Address&#xff0c;网络协议地址&#xff09;是用于表示网络节点的逻辑地址…

黄石首家Pearson VUE国际认证考试中心落户湖北理工学院

Pearson VUE 作为 Pearson 集团的专门从事计算机化考试服务的公司&#xff0c;到目前为止&#xff0c;已在全世界165 个国家授权了 4400 多个考试中心以及超过 230 家 PVUE 自有考试中心&#xff0c;其中在中国的有三百多个授权考点和 4 个自有考试中心。Pearson VUE 以其技术和…

虚拟列表 vue-virtual-scroller 的使用

npm 详情&#xff1a;vue-virtual-scroller - npm (npmjs.com) 这里我使用的是RecycleScroller。 App.vue <template><RecycleScrollerclass"scroller":items"items":item-size"54"v-slot"{ item }"><list-item :it…

『光谷云智慧大屏』数字智慧型 案例赏析

设计背景 随着数字化转型的不断深入&#xff0c;智慧大屏在云数据处理中心扮演着越来越重要的角色。大屏采用先进技术&#xff0c;构建了一个直观且互动的实时数据监控系统。它不仅提高了数据处理的效率&#xff0c;还为数据中心的智能化管理和运营提供了强有力的支持&#xf…

智慧工地势在必行,可以减少管理的无序状态,效率倍增。

智慧工地的建设对于提高工地管理效率和减少无序状态具有重要意义。智慧工地通过引入先进的信息技术和智能化设备&#xff0c;可以实现以下方面的优势&#xff1a; 1. 实时监测和管理&#xff1a; 智慧工地可以通过传感器、监控摄像头等设备对施工现场的各种参数和情况进行实时…

RGMII接口及时序详解

文章目录 一、RGMII接口介绍二、MAC和PHY1.关系2.MAC和PHY的交互3.MAC为什么要配置PHY4.如何配置&#xff08;1&#xff09;原理&#xff08;2&#xff09;PHY地址的确定&#xff08;3&#xff09;寄存器地址及配置数据 三、88E1111中PHY的时序1.接收端时序&#xff08;1&#…

Windows安全应急--应急排查的一些方法

前言&#xff1a; 非法BC植入网站安全应急&#xff0c; 在安全应急中&#xff0c; 总会需要大大小小的问题&#xff0c; 就像成长一样。 检测工具尽量使用轻量级的。。 本次演示环境 Windows Server 2008 问题排查步骤&#xff1a; 先判断服务器有没有被Rootkit 查看登录…

将联表查询到的数据按1000一批次存入数据库-模板

idea模板&#xff1a; /*** ${Shitilei}信息 服务层实现。** author admin*/ Service RequiredArgsConstructor public class Operate${Shitilei}Service {private final ${Shitilei}Mapper ${shitilei}Mapper;private final RegionUtil util;/*** ${shitilei}表* return 操作结…

【抽代复习笔记】18-置换练习题(2)及两个重要定理

最近一直忙于学校的事情&#xff0c;好久没更新了&#xff0c;实在抱歉。接下来几期大概也会更得慢一些&#xff0c;望见谅。 练习4&#xff1a;写出4次对称群S4中所有置换。 解&#xff1a;由上一篇笔记结尾的定理我们知道&#xff0c;4次对称群的阶&#xff08;也就是所含元…

SAP-有历史业务情况下的物料批次切换前提条件和方案建议

转载自&#xff1a;SAP-有历史业务情况下的物料批次切换前提条件和方案建议 - 知乎 (zhihu.com) 在SAP中&#xff0c;物料是否启用批次管理主要是通过物料上的“批次管理”字段进行控制&#xff0c;但物料批次管理涉及库存很多业务节点的控制&#xff0c;因此一旦有业务的发生&…

超市生鲜如何持续盈利?

生鲜经营是现代超市功能配置中不可缺少的组成部分&#xff0c;通过生鲜区经营及其效果可以反映出超市业态发展的成熟化程度。做好了可以达到集客和盈利的目的&#xff0c;做得不好&#xff0c;也很容易成为超市中的亏损&#xff0c;更难指望顾客经常光顾。 超市生鲜区生存和发展…