真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

grpc錯(cuò)誤處理

0.1、索引

https://waterflow.link/articles/

創(chuàng)新互聯(lián)主營遂川網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,成都App制作,遂川h5微信小程序搭建,遂川網(wǎng)站營銷推廣歡迎遂川等地區(qū)企業(yè)咨詢

我們都知道當(dāng)發(fā)起http請(qǐng)求的時(shí)候,服務(wù)端會(huì)返回一些http狀態(tài)碼,不管是成功還是失敗??蛻舳丝梢愿鶕?jù)服務(wù)端返回的狀態(tài)碼,判斷服務(wù)器出現(xiàn)了哪些錯(cuò)誤。

我們經(jīng)常用到的比如下面這些:

  • 200:OK,請(qǐng)求成功
  • 204:NO CONTENT,此請(qǐng)求沒有要發(fā)送的內(nèi)容,但標(biāo)頭可能很有用。 用戶代理可以用新的更新其緩存的資源頭。
  • 400:Bad Request,由于被認(rèn)為是客戶端錯(cuò)誤(例如,格式錯(cuò)誤的請(qǐng)求語法、無效的請(qǐng)求消息幀或欺騙性請(qǐng)求路由),服務(wù)器無法或不會(huì)處理請(qǐng)求。
  • 404:Not Found,服務(wù)器找不到請(qǐng)求的資源。 在瀏覽器中,這意味著無法識(shí)別 URL。 在 API 中,這也可能意味著端點(diǎn)有效但資源本身不存在。

同樣的,當(dāng)我們調(diào)用 gRPC 調(diào)用時(shí),客戶端會(huì)收到帶有成功狀態(tài)的響應(yīng)或帶有相應(yīng)錯(cuò)誤狀態(tài)的錯(cuò)誤。 客戶端應(yīng)用程序需要以能夠處理所有潛在錯(cuò)誤和錯(cuò)誤條件的方式編寫。 服務(wù)器應(yīng)用程序要求您處理錯(cuò)誤并生成具有相應(yīng)狀態(tài)代碼的適當(dāng)錯(cuò)誤。

發(fā)生錯(cuò)誤時(shí),gRPC 會(huì)返回其錯(cuò)誤狀態(tài)代碼之一以及可選的錯(cuò)誤消息,該消息提供錯(cuò)誤條件的更多詳細(xì)信息。 狀態(tài)對(duì)象由一個(gè)整數(shù)代碼和一個(gè)字符串消息組成,這些消息對(duì)于不同語言的所有 gRPC 實(shí)現(xiàn)都是通用的。

gRPC 使用一組定義明確的 gRPC 特定狀態(tài)代碼。 這包括如下狀態(tài)代碼:

  • OK:成功狀態(tài),不是錯(cuò)誤。
  • CANCELLED:操作被取消,通常是由調(diào)用者取消的。
  • DEADLINE_EXCEEDED:截止日期在操作完成之前到期。
  • INVALID_ARGUMENT:客戶端指定了無效參數(shù)。

詳細(xì)的狀態(tài)code、number和解釋可以參考這里:https://github.com/grpc/grpc/blob/master/doc/statuscodes.md

1、grpc錯(cuò)誤

之前的章節(jié)中我們寫過關(guān)于簡(jiǎn)單搭建grpc的文章:https://waterflow.link/articles/

我們?cè)谶@個(gè)基礎(chǔ)上稍微修改一下,看下下面的例子。

首先我們?cè)诜?wù)端,修改下代碼,在service的Hello方法中加個(gè)判斷,如果客戶端傳過來的不是hello,我們我們將返回grpc的標(biāo)準(zhǔn)錯(cuò)誤。像下面這樣:

func (h HelloService) Hello(ctx context.Context, args *String) (*String, error) {
	time.Sleep(time.Second)
  // 返回參數(shù)不合法的錯(cuò)誤
	if args.GetValue() != "hello" {
		return nil, status.Error(codes.InvalidArgument, "請(qǐng)求參數(shù)錯(cuò)誤")
	}
	reply := &String{Value: "hello:" + args.GetValue()}
	return reply, nil
}

我們客戶端的代碼像下面這樣:

func unaryRpc(conn *grpc.ClientConn) {
	client := helloservice.NewHelloServiceClient(conn)
	ctx := context.Background()
	md := metadata.Pairs("authorization", "mytoken")
	ctx = metadata.NewOutgoingContext(ctx, md)
  // 調(diào)用Hello方法,并傳入字符串hello
	reply, err := client.Hello(ctx, &helloservice.String{Value: "hello"})
	if err != nil {
		log.Fatal(err)
	}
	log.Println("unaryRpc recv: ", reply.Value)
}

我們開啟下服務(wù)端,并運(yùn)行客戶端代碼:

go run helloclient/main.go    
invoker request time duration:  1
2022/10/16 23:05:18 unaryRpc recv:  hello:hello

可以看到會(huì)輸出正確的結(jié)果?,F(xiàn)在我們修改下客戶端代碼:

func unaryRpc(conn *grpc.ClientConn) {
	client := helloservice.NewHelloServiceClient(conn)
	ctx := context.Background()
	md := metadata.Pairs("authorization", "mytoken")
	ctx = metadata.NewOutgoingContext(ctx, md)
  // 調(diào)用Hello方法,并傳入字符串f**k
	reply, err := client.Hello(ctx, &helloservice.String{Value: "f**k"})
	if err != nil {
		log.Fatal(err)
	}
	log.Println("unaryRpc recv: ", reply.Value)
}

然后運(yùn)行下客戶端代碼:

go run helloclient/main.go
invoker request time duration:  1
2022/10/16 23:14:13 rpc error: code = InvalidArgument desc = 請(qǐng)求參數(shù)錯(cuò)誤
exit status 1

可以看到我們獲取到了服務(wù)端返回的錯(cuò)誤。

2、獲取grpc錯(cuò)誤類型

有時(shí)候客戶端通過服務(wù)端返回的不同錯(cuò)誤類型去做一些具體的處理,這個(gè)時(shí)候客戶端可以這么寫:

func unaryRpc(conn *grpc.ClientConn) {
	client := helloservice.NewHelloServiceClient(conn)
	ctx := context.Background()
	md := metadata.Pairs("authorization", "mytoken")
	ctx = metadata.NewOutgoingContext(ctx, md)
	reply, err := client.Hello(ctx, &helloservice.String{Value: "f**k"})
	if err != nil {
		fromError, ok := status.FromError(err)
		if !ok {
			log.Fatal(err)
		}
    // 判斷服務(wù)端返回的是否是指定code的錯(cuò)誤
		if fromError.Code() == codes.InvalidArgument {
			log.Fatal("invalid arguments")
		}
	}
	log.Println("unaryRpc recv: ", reply.Value)
}

我們可以看下status.FromError的返回結(jié)果:

  • 如果 err 是由這個(gè)包產(chǎn)生的或者實(shí)現(xiàn)了方法 GRPCStatus() *Status,返回相應(yīng)的狀態(tài)。
  • 如果 err 為 nil,則返回帶有代碼的狀態(tài)。OK 并且沒有消息。
  • 否則,err 是與此包不兼容的錯(cuò)誤。 在這個(gè)情況下,返回一個(gè) Status 結(jié)構(gòu)是 code.Unknown 和 err 的 Error() 消息,并且ok為false。

我們重新執(zhí)行下客戶端代碼:

go run helloclient/main.go
invoker request time duration:  1
2022/10/16 23:26:11 invalid arguments
exit status 1

可以看到,當(dāng)服務(wù)端返回的是codes.InvalidArgument錯(cuò)誤時(shí),我們重新定義了錯(cuò)誤。

3、獲取grpc錯(cuò)誤更詳細(xì)的信息

當(dāng)我們服務(wù)端返回grpc錯(cuò)誤時(shí),我們想帶上一些自定義的詳細(xì)錯(cuò)誤信息,這個(gè)時(shí)候就可以像下面這樣寫:

func (h HelloService) Hello(ctx context.Context, args *String) (*String, error) {
	time.Sleep(time.Second)
	if args.GetValue() != "hello" {
		errorStatus := status.New(codes.InvalidArgument, "請(qǐng)求參數(shù)錯(cuò)誤")
		details, err := errorStatus.WithDetails(&errdetails.BadRequest_FieldViolation{
			Field:       "string.value",
			Description: fmt.Sprintf("expect hello, get %s", args.GetValue()),
		})
		if err != nil {
			return nil, errorStatus.Err()
		}
		return nil, details.Err()
	}
	reply := &String{Value: "hello:" + args.GetValue()}
	return reply, nil
}

我們重點(diǎn)看下WithDetails方法:

  • 該方法傳入一個(gè)proto.Message類型的數(shù)組,Message是一個(gè)protocol buffer的消息
  • 返回一個(gè)新Status,并將提供的詳細(xì)信息消息附加到Status
  • 如果遇到任何錯(cuò)誤,則返回 nil 和遇到的第一個(gè)錯(cuò)誤

然后我們修改下客戶端代碼:

func unaryRpc(conn *grpc.ClientConn) {
	client := helloservice.NewHelloServiceClient(conn)
	ctx := context.Background()
	md := metadata.Pairs("authorization", "mytoken")
	ctx = metadata.NewOutgoingContext(ctx, md)
	reply, err := client.Hello(ctx, &helloservice.String{Value: "f**k"})
	if err != nil {
		fromError, ok := status.FromError(err)
		if !ok {
			log.Fatal(err)
		}
		if fromError.Code() == codes.InvalidArgument {
      // 獲取錯(cuò)誤的詳細(xì)信息,因?yàn)樵敿?xì)信息返回的是數(shù)組,所以這里我們需要遍歷
			for _, detail := range fromError.Details() {
				detail = detail.(*proto.Message)
				log.Println(detail)
			}
			log.Fatal("invalid arguments")
		}
	}
	log.Println("unaryRpc recv: ", reply.Value)
}

接著重啟下服務(wù)端,運(yùn)行下客戶端代碼:

go run helloclient/main.go
invoker request time duration:  1
2022/10/16 23:58:51 field:"string.value"  description:"expect hello, get f**k"
2022/10/16 23:58:51 invalid arguments
exit status 1

可以看到詳細(xì)信息打印出來了。

4、定義標(biāo)準(zhǔn)錯(cuò)誤之外的錯(cuò)誤

現(xiàn)實(shí)中我們可能會(huì)有這樣的要求:

  • 當(dāng)grpc服務(wù)端是自定義錯(cuò)誤時(shí),客戶端返回自定義錯(cuò)誤
  • 當(dāng)grpc服務(wù)端返回的是標(biāo)準(zhǔn)錯(cuò)誤時(shí),客戶端返回系統(tǒng)錯(cuò)誤

我們可以創(chuàng)建一個(gè)自定義測(cè)錯(cuò)誤類:

package xerr

import (
	"fmt"
)

/**
常用通用固定錯(cuò)誤
*/
type CodeError struct {
	errCode uint32
	errMsg  string
}

//返回給前端的錯(cuò)誤碼
func (e *CodeError) GetErrCode() uint32 {
	return e.errCode
}

//返回給前端顯示端錯(cuò)誤信息
func (e *CodeError) GetErrMsg() string {
	return e.errMsg
}

func (e *CodeError) Error() string {
	return fmt.Sprintf("ErrCode:%d,ErrMsg:%s", e.errCode, e.errMsg)
}

然后grpc服務(wù)端實(shí)現(xiàn)一個(gè)攔截器,目的是把自定義錯(cuò)誤轉(zhuǎn)換成grpc錯(cuò)誤:

func LoggerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {

	resp, err = handler(ctx, req)
	if err != nil {
		causeErr := errors.Cause(err)                // err類型
		if e, ok := causeErr.(*xerr.CodeError); ok { //自定義錯(cuò)誤類型

			//轉(zhuǎn)成grpc err
			err = status.Error(codes.Code(e.GetErrCode()), e.GetErrMsg())
		} 

	}

	return resp, err
}

然后客戶端處理錯(cuò)誤代碼的部分修改如下:

//錯(cuò)誤返回
	
		causeErr := errors.Cause(err)                // err類型
		if e, ok := causeErr.(*xerr.CodeError); ok { //自定義錯(cuò)誤類型
			//自定義CodeError
			errcode = e.GetErrCode()
			errmsg = e.GetErrMsg()
		} else {
			errcode := uint32(500)
			errmsg := "系統(tǒng)錯(cuò)誤"
		}

其中用到的errors.Cause的作用就是遞歸獲取根錯(cuò)誤。

這其實(shí)就是go-zero中實(shí)現(xiàn)自定義錯(cuò)誤的方式,大家可以自己寫下試試吧。


文章題目:grpc錯(cuò)誤處理
本文網(wǎng)址:http://weahome.cn/article/dsoidhs.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部