status 是一个微服务常用的状态码管理工具,参考 google Status 规范设计。 可以在Http和GRPC服务中使用。
下面是一个表格,其中包含google.rpc.Code中定义的所有gRPC错误代码及其原因的简短说明。
HTTP | RPC | 描述 |
---|---|---|
200 | OK | 没有错误 |
400 | INVALID_ARGUMENT | 客户端指定了无效的参数。 检查错误消息和错误详细信息以获取更多信息。 |
400 | FAILED_PRECONDITION | 请求不能在当前系统状态下执行,例如删除非空目录。 |
400 | OUT_OF_RANGE | 客户端指定了无效的范围。 |
401 | UNAUTHENTICATED | 由于遗失,无效或过期的OAuth令牌而导致请求未通过身份验证。 |
403 | PERMISSION_DENIED | 客户端没有足够的权限。这可能是因为OAuth令牌没有正确的范围,客户端没有权限,或者客户端项目尚未启用API。 |
404 | NOT_FOUND | 找不到指定的资源,或者该请求被未公开的原因(例如白名单)拒绝。 |
409 | ABORTED | 并发冲突,例如读-修改-写冲突。 |
409 | ALREADY_EXISTS | 客户端尝试创建的资源已存在。 |
429 | RESOURCE_EXHAUSTED | 资源配额达到速率限制。 客户端应该查找google.rpc.QuotaFailure错误详细信息以获取更多信息。 |
499 | CANCELLED | 客户端取消请求 |
500 | DATA_LOSS | 不可恢复的数据丢失或数据损坏。 客户端应该向用户报告错误。 |
500 | UNKNOWN | 未知的服务器错误。 通常是服务器错误。 |
500 | INTERNAL | 内部服务错误。 通常是服务器错误。 |
501 | NOT_IMPLEMENTED | 服务器未实现该API方法。 |
503 | UNAVAILABLE | 暂停服务。通常是服务器已经关闭。 |
504 | DEADLINE_EXCEEDED | 已超过请求期限。如果重复发生,请考虑降低请求的复杂性。 |
要处理错误,您可以检查返回状态码的描述,并相应地修改您的请求。
go get github.com/go-leo/status/cmd/proto-gen-status@latest
syntax = "proto3";
package leo.example.status.errors;
option go_package = "github.com/go-leo/status/example/api/status/v1;status";
import "leo/status/annotations.proto";
enum Errors {
option (leo.status.default_rpc_status) = INTERNAL;
option (leo.status.default_http_status) = 500;
Default = 0;
JustRpcStatus = 1 [ (leo.status.rpc_status) = INVALID_ARGUMENT ];
JustHttpStatus = 2 [ (leo.status.http_status) = 400 ];
JustMessage = 3 [ (leo.status.message) = "just message" ];
AllHave = 4 [
(leo.status.rpc_status) = INVALID_ARGUMENT,
(leo.status.http_status) = 401,
(leo.status.message) = "all have"
];
}
注意:
- 枚举类型的
default_rpc_status
和default_http_status
需要配置,否则代码生成器跳过此枚举类型 - 如果枚举值指定了
rpc_status
(http_status
), 则使用指定的rpc_status
(http_status
),否则使用default_rpc_status
(default_http_status
)
protoc \
--proto_path=. \
--proto_path=../proto/ \
--proto_path=../third_party \
--go_out=. \
--go_opt=paths=source_relative \
--status_out=. \
--status_opt=paths=source_relative \
*/*.proto
注意事项:
- proto/leo/status需要根据实际目录按需放置
// Code generated by protoc-gen-status. DO NOT EDIT.
package status
import (
status "github.com/go-leo/status"
codes "google.golang.org/grpc/codes"
)
var clean_ErrDefault = ErrDefault()
func ErrDefault(opts ...status.Option) status.Status {
return status.New(codes.Internal, append([]status.Option{status.HttpStatus(500), status.Identifier("Errors_Default"), status.Message("")}, opts...)...)
}
func IsDefault(err error) (status.Status, bool) {
st, ok := status.From(err)
if !ok {
return st, false
}
return st, clean_ErrDefault.Is(st)
}
var clean_ErrJustRpcStatus = ErrJustRpcStatus()
func ErrJustRpcStatus(opts ...status.Option) status.Status {
return status.New(codes.InvalidArgument, append([]status.Option{status.HttpStatus(500), status.Identifier("Errors_JustRpcStatus"), status.Message("")}, opts...)...)
}
func IsJustRpcStatus(err error) (status.Status, bool) {
st, ok := status.From(err)
if !ok {
return st, false
}
return st, clean_ErrJustRpcStatus.Is(st)
}
var clean_ErrJustHttpStatus = ErrJustHttpStatus()
func ErrJustHttpStatus(opts ...status.Option) status.Status {
return status.New(codes.Internal, append([]status.Option{status.HttpStatus(400), status.Identifier("Errors_JustHttpStatus"), status.Message("")}, opts...)...)
}
func IsJustHttpStatus(err error) (status.Status, bool) {
st, ok := status.From(err)
if !ok {
return st, false
}
return st, clean_ErrJustHttpStatus.Is(st)
}
var clean_ErrJustMessage = ErrJustMessage()
func ErrJustMessage(opts ...status.Option) status.Status {
return status.New(codes.Internal, append([]status.Option{status.HttpStatus(500), status.Identifier("Errors_JustMessage"), status.Message("just message")}, opts...)...)
}
func IsJustMessage(err error) (status.Status, bool) {
st, ok := status.From(err)
if !ok {
return st, false
}
return st, clean_ErrJustMessage.Is(st)
}
var clean_ErrAllHave = ErrAllHave()
func ErrAllHave(opts ...status.Option) status.Status {
return status.New(codes.InvalidArgument, append([]status.Option{status.HttpStatus(401), status.Identifier("Errors_AllHave"), status.Message("all have")}, opts...)...)
}
func IsAllHave(err error) (status.Status, bool) {
st, ok := status.From(err)
if !ok {
return st, false
}
return st, clean_ErrAllHave.Is(st)
}
注意事项:
ErrInvalidPassword
会创建并返回一个status.Status
IsInvalidPassword
会判断传入的error
是否是ErrInvalidPassword
错误,在没有修改Identifier
情况下是相同的
type server struct {
helloworldpb.UnimplementedGreeterServer
}
func (s *server) SayHello(_ context.Context, in *helloworldpb.HelloRequest) (*helloworldpb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
switch in.GetName() {
case "Default":
return nil, statuspb.ErrDefault(status.ErrorInfo("reason", "domain", map[string]string{"key": "value"}))
case "JustRpcStatus":
return nil, statuspb.ErrJustRpcStatus(status.RetryInfo(time.Second))
case "JustHttpStatus":
return nil, statuspb.ErrJustHttpStatus(status.DebugInfo([]string{"stack entry"}, "stack entry"))
case "JustMessage":
return nil, statuspb.ErrJustMessage(status.QuotaFailure([]*errdetails.QuotaFailure_Violation{{Subject: "subject", Description: "description"}}))
case "AllHave":
return nil, statuspb.ErrAllHave(status.PreconditionFailure([]*errdetails.PreconditionFailure_Violation{{Subject: "subject", Description: "description"}}))
case "Custom":
return nil, status.New(
codes.Unknown,
status.Message("custom message"),
status.BadRequest([]*errdetails.BadRequest_FieldViolation{{Field: "field", Description: "description"}}),
status.RequestInfo("request_id", "serving_data"),
status.ResourceInfo("resource_type", "resource_name", "owner", "description"),
status.Help([]*errdetails.Help_Link{{Url: "url", Description: "description"}}),
status.LocalizedMessage("locale", "message"),
)
}
return &helloworldpb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
运行Servergo run ./main.go
r, err := c.SayHello(ctx, &helloworldpb.HelloRequest{Name: *name})
if err != nil {
var st status.Status
var ok bool
if st, ok = statuspb.IsDefault(err); ok {
jsonData, _ := st.MarshalJSON()
log.Fatalf("default error: %v, json: %s", st, jsonData)
} else if st, ok = statuspb.IsJustRpcStatus(err); ok {
jsonData, _ := st.MarshalJSON()
log.Fatalf("just rpc status error: %v, json: %s", st, jsonData)
} else if st, ok = statuspb.IsJustHttpStatus(err); ok {
jsonData, _ := st.MarshalJSON()
log.Fatalf("just http status error: %v, json: %s", st, jsonData)
} else if st, ok = statuspb.IsJustMessage(err); ok {
jsonData, _ := st.MarshalJSON()
log.Fatalf("just message error: %v, json: %s", st, jsonData)
} else if st, ok = statuspb.IsAllHave(err); ok {
jsonData, _ := st.MarshalJSON()
log.Fatalf("all have error: %v, json: %s", st, jsonData)
} else {
jsonData, _ := st.MarshalJSON()
log.Fatalf("custom error: %v, json: %s", st, jsonData)
}
}
log.Printf("Greeting: %s", r.GetMessage())
运行Client
go run ./main.go -name Default
go run ./main.go -name JustRpcStatus
go run ./main.go -name JustHttpStatus
go run ./main.go -name JustMessage
go run ./main.go -name AllHave
go run ./main.go -name Custom
完成gRPC例子见grpc
mux := http.NewServeMux()
mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
content, _ := io.ReadAll(r.Body)
name := string(content)
log.Printf("Received: %v", name)
var st status.Status
switch name {
case "Default":
st = statuspb.ErrDefault(status.ErrorInfo("reason", "domain", map[string]string{"key": "value"}), status.Headers(http.Header{"key": []string{"value"}}))
case "JustRpcStatus":
st = statuspb.ErrJustRpcStatus(status.RetryInfo(time.Second), status.Headers(http.Header{"key": []string{"value"}}))
case "JustHttpStatus":
st = statuspb.ErrJustHttpStatus(status.DebugInfo([]string{"stack entry"}, "stack entry"), status.Headers(http.Header{"key": []string{"value"}}))
case "JustMessage":
st = statuspb.ErrJustMessage(status.QuotaFailure([]*errdetails.QuotaFailure_Violation{{Subject: "subject", Description: "description"}}), status.Headers(http.Header{"key": []string{"value"}}))
case "AllHave":
st = statuspb.ErrAllHave(status.PreconditionFailure([]*errdetails.PreconditionFailure_Violation{{Subject: "subject", Description: "description"}}), status.Headers(http.Header{"key": []string{"value"}}))
case "Custom":
st = status.New(
codes.Unknown,
status.Message("custom message"),
status.BadRequest([]*errdetails.BadRequest_FieldViolation{{Field: "field", Description: "description"}}),
status.RequestInfo("request_id", "serving_data"),
status.ResourceInfo("resource_type", "resource_name", "owner", "description"),
status.Help([]*errdetails.Help_Link{{Url: "url", Description: "description"}}),
status.LocalizedMessage("locale", "message"),
status.Headers(http.Header{"key": []string{"value"}}),
)
}
if st == nil {
_, _ = w.Write([]byte("Hello " + name))
return
}
var contentType string
var body []byte
if jsonBody, marshalErr := st.MarshalJSON(); marshalErr == nil {
contentType, body = "application/json; charset=utf-8", jsonBody
}
w.Header().Set("Content-Type", contentType)
for k, values := range st.Headers() {
for _, v := range values {
w.Header().Add(k, v)
}
}
w.WriteHeader(st.StatusCode())
_, _ = w.Write(body)
})
运行Servergo run ./main.go
resp, err := http.Post("http://"+*addr+"/hello", "text/plain", bytes.NewBuffer([]byte(*name)))
if err != nil {
log.Fatalf("could not greet: %v", err)
}
err, ok := status.From(resp)
if ok {
var st status.Status
var ok bool
if st, ok = statuspb.IsDefault(err); ok {
jsonData, _ := st.MarshalJSON()
log.Fatalf("default error: %v, json: %s, header: %v", st, jsonData, st.Headers())
} else if st, ok = statuspb.IsJustRpcStatus(err); ok {
jsonData, _ := st.MarshalJSON()
log.Fatalf("just rpc status error: %v, json: %s, header: %v", st, jsonData, st.Headers())
} else if st, ok = statuspb.IsJustHttpStatus(err); ok {
jsonData, _ := st.MarshalJSON()
log.Fatalf("just http status error: %v, json: %s, header: %v", st, jsonData, st.Headers())
} else if st, ok = statuspb.IsJustMessage(err); ok {
jsonData, _ := st.MarshalJSON()
log.Fatalf("just message error: %v, json: %s, header: %v", st, jsonData, st.Headers())
} else if st, ok = statuspb.IsAllHave(err); ok {
jsonData, _ := st.MarshalJSON()
log.Fatalf("all have error: %v, json: %s, header: %v", st, jsonData, st.Headers())
} else {
jsonData, _ := st.MarshalJSON()
log.Fatalf("custom error: %v, json: %s, header: %v", st, jsonData, st.Headers())
}
}
message, _ := io.ReadAll(resp.Body)
log.Printf("Greeting: %s", message)
}
运行Client
go run ./main.go -name Default
go run ./main.go -name JustRpcStatus
go run ./main.go -name JustHttpStatus
go run ./main.go -name JustMessage
go run ./main.go -name AllHave
go run ./main.go -name Custom
完成gRPC例子见grpc