起因

在我们使用 kratos 开发 web 应用时,由于 kratos 默认设计导致我们在自定义错误时,访问 api 时,相应的状态码是以自定义的 error code 作为 http status code 表现的。

如果我们想将 业务状态码和 HTTP 状态码分离,即所有的 http status code 都为 200, 实际的业务状态码以在 response 中响应的 code 为主。

我们该如何实现呢? 有2种方法,一种是自定义中间件将错误进行特别处理,另一种是自定义 Encoder。 本篇文章主要就是介绍 Encoder 方法。

自定义 Encoder

根据我们的需要,我们需要自定义 2 个 Encoder, ErrorEncoderResponseEncoder 函数。 需要的参数类型分别如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 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 的结构体

1
2
3
4
5
6
7
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 的编码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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

1
2
	opts = append(opts, http.ErrorEncoder(Encoder.ErrorEncoder))
	opts = append(opts, http.ResponseEncoder(Encoder.ResponseEncoder))

4 运行代码,访问相应接口进行测试:

img1.png

img_1.png

参考