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 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
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/639758.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从零起航,Python编程全攻略

新书上架~👇全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我👆,收藏下次不迷路┗|`O′|┛ 嗷~~ 目录 一、Python入门之旅 二、Python进阶之道 三、Python爬虫实战 四、Python数据分析利器 五…

STL--set和multiset集合

set和multiset会根据特定的排序准则&#xff0c;自动将元素排序。两者不同之处在于multiset 允许元素重复而 set 不允许。如下图: 使用set或multiset&#xff0c;必须先包含头文件: #include <set>上述两个类型都被定义为命名空间std内的class template: namespace std…

挖掘抖快销售榜TOP500,这些单品正在引爆夏日市场!

凉鞋、短裤、草席、风扇……一个个夏日“限定”品类在4月就开始冲上抖音、快手两大电商的品类销售榜时&#xff0c;预示着夏日营销在春季已悄悄打响。 在炎炎夏日来临之前&#xff0c;品牌方们都会迎接一次夏日营销“大考”&#xff0c;铆足了劲调动消费者的积极性&#xff0c;…

揭秘Python的魔法:装饰器的超能力大揭秘 ‍♂️✨

文章目录 Python进阶之装饰器详解1. 引言装饰器的概念与意义装饰器在Python编程中的作用 2. 背景介绍2.1 函数作为对象2.2 高阶函数 3. 装饰器基础3.1 理解装饰器3.2 装饰器的工作原理 4. 带参数的装饰器4.1 为什么需要带参数4.2 实现带参数的装饰器使用函数包裹装饰器使用类实…

React开发环境配置详细讲解-04

React环境 前端随着规范化&#xff0c;可以说规范和环境插件配置满天飞&#xff0c;笔者最早接触的是jquery&#xff0c;那个开发非常简单&#xff0c;只要引入jquery就可以了&#xff0c;当时还写了一套UI框架&#xff0c;至今在做小型项目中还在使用&#xff0c;show一张效果…

小恐龙跳一跳源码

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

FL Studio2025中文最新版本专业编曲软件有哪些新功能?

FL Studio 21&#xff0c;也被音乐制作爱好者亲切地称为“水果编曲软件”&#xff0c;是比利时的Image-Line公司研发的一款完整的音乐制作环境或数字音频工作站&#xff08;DAW&#xff09;。自从1990年代推出以来&#xff0c;FL Studio 以其直观的用户界面、丰富的插件支持和强…

PHP质量工具系列之php_CodeSniffer

PHP_CodeSniffer 是一组两个 PHP 脚本&#xff1a;主脚本 phpcs 对 PHP、JavaScript 和 CSS 文件进行标记&#xff0c;以检测是否违反定义的编码标准&#xff1b;第二个脚本 phpcbf 自动纠正违反编码标准的行为。PHP_CodeSniffer 是一个重要的开发工具&#xff0c;可以确保你的…

【电路笔记】-有源高通滤波器

有源高通滤波器 文章目录 有源高通滤波器1、概述2、有源高通滤波器3、有源高通滤波器示例4、二阶高通有源滤波器有源高通滤波器可以通过将无源 RC 滤波器网络与运算放大器相结合来创建,以产生具有放大功能的高通滤波器。 1、概述 有源高通滤波器 (HPF) 的基本操作与其等效 RC…

【Crypto】摩丝

文章目录 一、摩斯解题感悟 一、摩斯 很明显莫尔斯密码 iloveyou还挺浪漫 小小flag&#xff0c;拿下 解题感悟 莫尔斯密码这种题还是比较明显的

游戏安全防控有招了! MMO游戏安全场景解决方案

2024年4月10日&#xff0c;暴雪娱乐与网易共同宣布&#xff1a;停服442天后&#xff0c;那款曾经让数百万国内玩家为之痴迷的MMO游戏《魔兽世界》国服要重新回归了。 还记得服务器关闭倒计时15分钟开始的时候&#xff0c;素不相识的大家就在频道中互相告别&#xff0c;“愿风指…

spring boot集成Knife4j

文章目录 一、Knife4j是什么&#xff1f;二、使用步骤1.引入依赖2.新增相关的配置类3.添加配置信息4.新建测试类5. 启动项目 三、其他版本集成时常见异常1. Failed to start bean ‘documentationPluginsBootstrapper2.访问地址后报404 一、Knife4j是什么&#xff1f; 前言&…

微服务项目收获和总结---第4天(文章审核和保存)

文章审核以及APP端保存文章 业务流程&#xff1a; App端保存接口&#xff1a; 数据库表详情 文章的基本信息表&#xff1a;id&#xff0c;标题&#xff0c;作者id&#xff0c;频道id...... 文章的权限/配置表&#xff1a;存储文章是否可以评论&#xff0c;是否上架&#xff…

在docker中运行SLAM十四讲程序

《十四讲》的示例程序依赖比较多&#xff0c;而且系统有点旧。可以在容器中运行。 拉取镜像 docker pull ddhogan/slambook:v0.1这个docker对应的github&#xff1a;HomeLH/slambook2-docker 拉下来之后&#xff0c;假如是Windows系统&#xff0c;需要使用XLaunch用于提供X11…

番外篇 | YOLOv5-SPD:用最简单的方式完成低分辨率图像和小目标检测升级

前言:Hello大家好,我是小哥谈。论文提出了一个新的CNN构建模块称为SPD-Conv,用来替换每个步长卷转层和每个池化层(从而完全消除它们)。SPD-Conv由一个空间到深度(SPD)层和一个非步长卷积(Conv)层组成。本文详细介绍了如何在YOLOv5中引入SPD-Conv,助力助力低分辨率与小…

掌握代码注释:提升代码可读性的秘密武器

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、为什么我们需要注释&#xff1f; 二、如何添加单行注释&#xff1f; 使用井号 # 添加单…

C++贪心算法(4)

过河的最短时间 #include<bits/stdc.h> using namespace std; int main() {int a[1010]{0};int n;cin>>n;for(int i0;i<n;i){cin>>a[i];}sort(a0,an);int xn-2;int yn-1;int tmpa[1];while(true){int tmp1a[0]a[y]a[1]a[1];int tmp2a[0]a[y]a[0]a[x];if(t…

Golang | Leetcode Golang题解之第110题平衡二叉树

题目&#xff1a; 题解&#xff1a; func isBalanced(root *TreeNode) bool {return height(root) > 0 }func height(root *TreeNode) int {if root nil {return 0}leftHeight : height(root.Left)rightHeight : height(root.Right)if leftHeight -1 || rightHeight -1 …

2024年收集搜索引擎蜘蛛大全以及浏览器模拟蜘蛛方法

对于做SEOer来说经常和搜索引擎蜘蛛打交道&#xff0c;下面整理收集了最全的搜索引擎蜘蛛大全。供有需要的朋友使用&#xff0c;建议收藏。 搜索引擎蜘蛛大全 "TencentTraveler", "Baiduspider", "BaiduGame", "bingbot",//必应蜘蛛…

MaxEnt模型文章中存在的问题和处理方法(050B更新)2024.5.24

目前多数MaxEnt文章中存在的问题和处理方案。 **问题一&#xff1a;**变量数据使用问题&#xff0c;很多文章把所有变量数据直接使用&#xff0c;但是温度和土壤、植被类型等属于不同数据类型&#xff0c;在数据使用时参数配置是不一样的&#xff0c;产生的结果文件也是不一样的…