gin排错记录 c.Next() c.Abrot()
[TOC]
记录一次哭笑不得的
gin排错记录
代码
// 业务Error |
Bug 表现
- 同样都是确定启用了
Auth的接口,- 比如一个
create接口,在token不对的情况下,会返回业务错误信息,HttpStatusCode为401 - 而出问题的这个
config接口(下文简称为问题接口),在token不对的情况下,虽然返回了业务错误信息,但是HttpStatusCode却是200
- 比如一个
相关技术介绍
gin路由注册,收到客户端请求,在提供服务的时候,是链式响应的。- 每个
handler和中间件都会被依次遍历到,逐个运行。
- 每个
gin最终处理请求的逻辑是在func (engine *Engine) handleHTTPRequest(c *Context)func (engine *Engine) handleHTTPRequest(c *Context) {
// ...
// Find root of the tree for the given HTTP method
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// Find route in tree
value := root.getValue(rPath, c.params, unescape)
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next() //执行handlers
c.writermem.WriteHeaderNow()
return
}
// ...
}
break
}
// ...
}可以看到在
engine函数里,调用了一句c.Next()// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in GitHub.
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c) //执行handler
c.index++
}
}从
Next()的代码里,可以看到它会遍历执行每个handler。当然,中间件也是handler。在官方注释中提到了两点:
Next()应该仅在中间件内部被调用。—— 意思是不是中间件代码,就尽量别显式调用这个函数。- 它执行调用处理程序内部链中的挂起处理程序。—— 这句话描述了
Next()在被调用以后会发生什么。
不讲理论,我们直接举个例子来描述
c.Next()的功效。- 如果现在有这样一个调用链
Handler1——print(1)Handler2——print(2)Handler3——print(3)Handler4——print(4)Handler5——print(5)
- 正常执行,应该打印出
1 2 3 4 5 - 但是如果,
Handler2的代码是这样的三句print(A)c.Next()print(B)
- 那么这时,会打印出
1 A 3 4 5 B—— 而不是1 A B 3 4 5
- 如果现在有这样一个调用链
c.Abort()和c.Next()都是用于调整handlers链的执行顺序的
// Abort prevents pending handlers from being called. Note that this will not stop the current handler. |
举个例子:
- 调用链和上面的一样
Handler2代码如下,会打印出:1 A Bprint(A)c.Abort()print(B)
Handler2代码如下,会打印出:1 A Bprint(A)c.Abort()c.Next()print(B)
gin中负责处理Response Header的函数在$GINPATH/response_writer.gow *responseWriter是gin.Context的一个成员属性func (w *responseWriter) WriteHeader(code int) {
if code > 0 && w.status != code {
if w.Written() {
debugPrint("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code)
}
w.status = code
}
}
理论已知,开始实践
1、去除 c.Next()
了解了 c.Next() 的原理之后,我们应该意识到,原代码中 Auth 函数最后一行的 c.Next() 因为处于函数最后,所以留着也没用
2、c.w.WriteHeader() 测试
我们在下面代码中的A、B、C三个位置插入 c.w.WriteHeader()
每次仅保留一个位置有效,另外两个位置注释掉,执行 handler 过程,查看HTTP状态码是否符合期望。
func (s *Server) Auth(c *gin.Context) { |
最终结论
- 上文的实践中可以发现,
c.JSON(ErrToken.HTTPCode, ErrToken)会将HTTP状态码覆写为200 - 如果补充一个实验
- 注释 A、B、C 三个位置
- 用
c.JSON(http.StatusUnauthorized, ErrToken)代替c.JSON(ErrToken.HTTPCode, ErrToken) - 实验结果将是,最终 http response 的HTTP状态码为
401
- 经过深入查看源代码,发现,
ErrToken.HTTPCode为空值。ErrToken构造函数里没赋值!