起因
在我们使用 kratos 开发 web 应用时,由于 kratos 默认设计导致我们在自定义错误时,访问 api 时,相应的状态码是以自定义的 error code 作为 http status code 表现的。
如果我们想将 业务状态码和 HTTP 状态码分离,即所有的 http status code 都为 200,实际的业务状态码以在 response 中响应的 code 为主。
我们该如何实现呢?有 2 种方法,一种是自定义中间件将错误进行特别处理,另一种是自定义 Encoder。本篇文章主要就是介绍 Encoder 方法。
自定义 Encoder
根据我们的需要,我们需要自定义 2 个 Encoder, ErrorEncoder
和 ResponseEncoder
函数。需要的参数类型分别如下:
// ResponseEncoder with response encoder.
func ResponseEncoder(en EncodeResponseFunc) ServerOption {
return func(o *Server) {
o.enc = en
}
}
// ErrorEncoder with error encoder.
func ErrorEncoder(en EncodeErrorFunc) ServerOption {
return func(o *Server) {
o.ene = en
}
}
// EncodeResponseFunc is encode response func.
type EncodeResponseFunc func(http.ResponseWriter, *http.Request, interface{}) error
// EncodeErrorFunc is encode error func.
type EncodeErrorFunc func(http.ResponseWriter, *http.Request, error)
1 自定义 Response 结构 定义一个 Response 的结构体
type Response struct {
Code int `json:"code" form:"code" protobuf:"varint,1,opt,name=code"`
Message string `json:"message" form:"message" protobuf:"bytes,2,opt,name=message"`
Ts string `json:"ts" form:"ts" protobuf:"bytes,3,opt,name=ts"`
Reason string `json:"reason" form:"reason" protobuf:"bytes,4,opt,name=reason"`
Data interface{} `json:"data" form:"data" protobuf:"bytes,5,opt,name=data"`
}
2 定义 2 个 Encoder 函数,分别对应 Response 和 Error 的编码
func ErrorEncoder(w stdhttp.ResponseWriter, r *stdhttp.Request, err error) {
se := errors.FromError(err)
reply := NewResponse()
reply.Code = int(se.Code)
reply.Data = nil
reply.Message = se.Message
reply.Reason = se.Reason
reply.Ts = time.Now().Format("2006-01-02 15:04:05.00000")
codec, _ := http.CodecForRequest(r, "Accept")
body, err := codec.Marshal(reply)
if err != nil {
w.WriteHeader(stdhttp.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", contentType(codec.Name()))
w.WriteHeader(stdhttp.StatusOK)
w.Write(body)
}
func ResponseEncoder(w stdhttp.ResponseWriter, r *stdhttp.Request, v interface{}) error {
reply := NewResponse()
reply.Code = 200
reply.Data = v
reply.Message = "success"
reply.Reason = "success"
reply.Ts = time.Now().Format("2006-01-02 15:04:05.00000")
codec, _ := http.CodecForRequest(r, "Accept")
data, err := codec.Marshal(reply)
if err != nil {
return err
}
w.Header().Set("Content-Type", contentType(codec.Name()))
w.WriteHeader(stdhttp.StatusOK)
w.Write(data)
return nil
}
3 在 http server 添加相应的 Encoder
opts = append(opts, http.ErrorEncoder(Encoder.ErrorEncoder))
opts = append(opts, http.ResponseEncoder(Encoder.ResponseEncoder))
4 运行代码,访问相应接口进行测试: