在我们的日常开发中,HTTP响应通常包含如下几个字段,但是kratos并没有去帮助我们封装这个响应体,而是靠我们手动构造
{ success: true, code: 200, msg: "success", data: [] }
常规响应
kratos没有统一的响应格式,都是我们程序员自定义的,如果说要更换相应格式,那么我们应该对其默认的响应结构体编码进行修改
默认使用的DefaultResponseEncoder
// DefaultResponseEncoder encodes the object to the HTTP response. func DefaultResponseEncoder(w http.ResponseWriter, r *http.Request, v interface{}) error { if v == nil { return nil } if rd, ok := v.(Redirector); ok { url, code := rd.Redirect() http.Redirect(w, r, url, code) return nil } codec, _ := CodecForRequest(r, "Accept") data, err := codec.Marshal(v) if err != nil { return err } w.Header().Set("Content-Type", httputil.ContentType(codec.Name())) _, err = w.Write(data) if err != nil { return err } return nil }
自定义ResponseEncoder
// 自定义响应格式 func ResponseEncoder(w http.ResponseWriter, r *http.Request, v interface{}) error { if v == nil { return nil } if rd, ok := v.(kratos_http.Redirector); ok { url, code := rd.Redirect() http.Redirect(w, r, url, code) return nil } codec, _ := kratos_http.CodecForRequest(r, "Accept") resp := &httpResponse{ Success: true, Code: 200, Msg: "success", Data: v, } data, err := codec.Marshal(resp) if err != nil { return err } w.Header().Set("Content-Type", "application/"+codec.Name()) _, err = w.Write(data) return err }
在internal/server/http.go下添加配置
// 自定义响应体 opts = append(opts, http.ResponseEncoder(ResponseEncoder)) srv := http.NewServer(opts...)
错误响应
// 自定义错误格式 func ErrorEncoder(w http.ResponseWriter, r *http.Request, err error) { resp := new(httpResponse) // 如果能从kratos error解析出来, 则使用对应的code和message作为错误响应 if gs, ok := status.FromError(err); ok { resp = &httpResponse{ Success: false, Code: kratos_status.FromGRPCCode(gs.Code()), Msg: gs.Message(), Data: nil, } } else { resp = &httpResponse{ Success: false, Code: http.StatusInternalServerError, Msg: err.Error(), Data: nil, } } codec, _ := kratos_http.CodecForRequest(r, "Accept") body, err := codec.Marshal(resp) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/"+codec.Name()) w.WriteHeader(resp.Code) // 响应状态码 _, _ = w.Write(body) }
在internal/server/http.go下添加配置
// 自定义错误响应 opts = append(opts, http.ErrorEncoder(ErrorEncoder))
首先先解析错误,看看是否符合grpc格式,如果符合将原本在grpc中的Code和Message写入自定义的结构体当中,如果不符合则返回默认的状态码500,并且写入响应体body当中
自定义错误状态码
https://go-kratos.dev/zh-cn/docs/component/errors/
安装kratos错误处理相关的proto包
go install github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2@latest
在api/shortvid-service/v1下新建errors.proto
syntax = "proto3"; option go_package = "shortvid-backend/api/shortvid-service/v1;v1"; import "errors/errors.proto"; enum ErrorReason { // 设置缺省错误码 option (errors.default_code) = 500; // 用户相关 USER_NOT_FOUND = 0 [(errors.code) = 404]; }
然后生成对应的客户端代码
Makefile中写入
.PHONY: errors # errors errors: protoc --proto_path=. \ --proto_path=./third_party \ --go_out=paths=source_relative:. \ --go-errors_out=paths=source_relative:. \ $(API_PROTO_FILES)
然后执行
make errors
在service层使用自定义的枚举错误常量
if userModel == nil { return nil, v1.ErrorUserNotFound("user not found") }
完整代码
server/encoder.go package server import ( "net/http" kratos_http "github.com/go-kratos/kratos/v2/transport/http" kratos_status "github.com/go-kratos/kratos/v2/transport/http/status" "google.golang.org/grpc/status" ) type httpResponse struct { Success bool `json:"success"` Code int `json:"code"` Msg string `json:"msg"` Data any `json:"data,omitempty"` } // 自定义响应格式 func ResponseEncoder(w http.ResponseWriter, r *http.Request, v interface{}) error { if v == nil { return nil } if rd, ok := v.(kratos_http.Redirector); ok { url, code := rd.Redirect() http.Redirect(w, r, url, code) return nil } codec, _ := kratos_http.CodecForRequest(r, "Accept") resp := &httpResponse{ Success: true, Code: 200, Msg: "success", Data: v, } data, err := codec.Marshal(resp) if err != nil { return err } w.Header().Set("Content-Type", "application/"+codec.Name()) _, err = w.Write(data) return err } // 自定义错误格式 func ErrorEncoder(w http.ResponseWriter, r *http.Request, err error) { resp := new(httpResponse) if gs, ok := status.FromError(err); ok { resp = &httpResponse{ Success: false, Code: kratos_status.FromGRPCCode(gs.Code()), Msg: gs.Message(), Data: nil, } } else { resp = &httpResponse{ Success: false, Code: http.StatusInternalServerError, Msg: err.Error(), Data: nil, } } codec, _ := kratos_http.CodecForRequest(r, "Accept") body, err := codec.Marshal(resp) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/"+codec.Name()) w.WriteHeader(resp.Code) // 响应状态码 _, _ = w.Write(body) } server/http.go package server import ( v1 "shortvid-backend/api/shortvid-service/v1" "shortvid-backend/app/shortvid-service/internal/conf" "shortvid-backend/app/shortvid-service/internal/service" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/middleware/recovery" "github.com/go-kratos/kratos/v2/transport/http" ) // NewHTTPServer new an HTTP server. func NewHTTPServer(c *conf.Server, users *service.UsersService, logger log.Logger) *http.Server { var opts = []http.ServerOption{ http.Middleware( recovery.Recovery(), ), } if c.Http.Network != "" { opts = append(opts, http.Network(c.Http.Network)) } if c.Http.Addr != "" { opts = append(opts, http.Address(c.Http.Addr)) } if c.Http.Timeout != nil { opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration())) } // 自定义响应格式 opts = append(opts, http.ResponseEncoder(ResponseEncoder)) opts = append(opts, http.ErrorEncoder(ErrorEncoder)) srv := http.NewServer(opts...) v1.RegisterUsersServiceHTTPServer(srv, users) return srv }