Understanding Gin Middleware Flow: Next(), Abort(), and the Onion Model

Gin Context Handling

// Gin context processing
func (engine *Engine) HandleContext(c *Context) {
  oldIndexValue := c.index
  c.reset()
  engine.handleHTTPRequest(c)
  
  c.index = oldIndexValue
}

// Route matching logic
func (engine *Engine) handleHTTPRequest(c *Context) {
  httpMethod := c.Request.Method
  rPath := c.Request.URL.Path
  unescape := false
  if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
    rPath = c.Request.URL.RawPath
    unescape = engine.UnescapePathValues
  }

  if engine.RemoveExtraSlash {
    rPath = cleanPath(rPath)
  }

  // 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, c.skippedNodes, unescape)
    if value.params != nil {
      c.Params = *value.params
    }
    if value.handlers != nil {
      c.handlers = value.handlers
      c.fullPath = value.fullPath
      // Start middleware and route method execution
      c.Next()
      c.writermem.WriteHeaderNow()
      return
    }
    if httpMethod != http.MethodConnect && rPath != "/" {
      if value.tsr && engine.RedirectTrailingSlash {
        redirectTrailingSlash(c)
        return
      }
      if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
        return
      }
    }
    break
  }

  if engine.HandleMethodNotAllowed {
    // According to RFC 7231 section 6.5.5, MUST generate an Allow header field in response
    // containing a list of the target resource's currently supported methods.
    allowed := make([]string, 0, len(t)-1)
    for _, tree := range engine.trees {
      if tree.method == httpMethod {
        continue
      }
      if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
        allowed = append(allowed, tree.method)
      }
    }
    if len(allowed) > 0 {
      c.handlers = engine.allNoMethod
      c.writermem.Header().Set("Allow", strings.Join(allowed, ", "))
      serveError(c, http.StatusMethodNotAllowed, default405Body)
      return
    }
  }

  c.handlers = engine.allNoRoute
  serveError(c, http.StatusNotFound, default404Body)
}

c.Next() Implementation

func (c *Context) Next() {
  c.index++  // index is int8 type, initial value is -1
  for c.index < int8(len(c.handlers)) {
    c.handlers[c.index](c)
    c.index++
  }
}

// handlers include middleware and route methods
/*
Text demonstration:
// Assume gin.context passes through 3 middleware, one route method, and all middleware call c.Next() without interruption.
// From the handleHTTPRequest method code, we know that before entering middleware, c.Next() is called once, invoking the first middleware, i.e., c.handlers[0], where index is 0.
// After the first middleware calls c.Next(), index increments by 1, becoming 1, then c.handler[1] is called, entering the second middleware,
// After the second middleware calls c.Next(), index increments by 1 again, becoming 2, then c.handler[2] is called, entering the third middleware,
// After the third middleware calls c.Next(), index increments by 1 again, becoming 3, then c.handler[3] is called, entering the route method.
// After the route method execution completes, c.handlers[c.index](c) is executed first, then index increments by 1, becoming 4,
// At this point, index < int8(len(c.handlers)) is false, so we exit the current loop,
// Since the current loop is called from the previous loop, after exiting the current loop, c.index++ of the upper level is called, making index 5
// And so on, after exiting all loops, index will be incremented to 7.
// Summary, the final index size is "-1 + (len(c.handlers) * 2)".
// Since index is int8 type, the maximum positive value is 127, so len(c.handlers) cannot be greater than 63,
// Therefore, by default design, the number of middleware cannot exceed 62, otherwise chaos will occur.
*/

c.Abort() Implementation

func (c *Context) Abort() {
  c.index = abortIndex  // abortIndex = math.MaxInt8 >> 1, which is 127
}

/*
Set index to the maximum value of int8, so that in the next
for c.index < int8(len(c.handlers)){
...
}
loop, it will no longer enter the call to c.handlers[n], achieving the effect of preventing subsequent middleware/route execution.
Similarly, this design still depends on the number of middleware not exceeding 62, otherwise chaos will occur.
*/

c.Next() & c.Abort() Summary

/*
The two methods work together through index to achieve control over the context.
Summary as follows:
  1. By default, the number of middleware cannot exceed 62.
  2. c.Abort() is only effective for middleware that haven't called c.Next() yet,
    For middleware that have already called c.Next(),
    the code before c.Next() has already been executed and is not affected,
    the code after c.Next() will still be executed and is not affected, because before exiting the loop, c.handlers[c.index](c) must be executed first.
  3. c.Abort() does not terminate the current middleware's code, you need to add return to end the current middleware call.
*/

The Onion Model

The onion model in middleware execution refers to how requests pass through multiple layers of middleware, similar to passing through layers of an onion. Each middleware can perform operations before and after passing the request to the next layer.

For a visual representation of this model, refer to resources that illustrate middleware execution flow in web frameworks.

Tags: Gin Go middleware Next Abort

Posted on Thu, 28 May 2026 16:40:28 +0000 by kriss37