【问题标题】:What is the best way to send back the response from the middle-ware in Golang从Golang的中间件发回响应的最佳方式是什么
【发布时间】:2018-02-21 06:16:47
【问题描述】:

我已经使用适配器模式创建了中间件。我的中间件之一是用于身份验证。因此,如果用户未获得授权,那么我必须向用户发回响应,并且不应调用下一个中间件。

// Adapter type

type Adapter func(http.Handler) http.Handler

// Adapt func
func Adapt(h http.Handler, adapters ...Adapter) http.Handler {
    // Call all middleware
    for _, adapter := range adapters {
        h = adapter(h)
    }
    return h
}

// CheckAuth middleware
func CheckAuth() Adapter {
    return func(h http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // Get Authorization token from the header
            // Validate the token
            // if valid token then call h.ServeHTTP(w, r)
            // else send response 401 to the user,
            if(validUser){
                h.ServeHTTP(w, r)
            }else{
                fmt.Fprintf(w, "Unauthorized")
            }
            return h
        }
    }
}

http.Handle("/", Adapt(indexHandler, AddHeader(),
                                 CheckAuth(),
                                 CopyMgoSession(db),
                                 Notify(logger), 
                               )

在 CheckAuth 中间件中,我仅在用户获得授权时才调用 h.ServeHTTP(w, r),因此对于 else 条件,我们还需要打破 Adapt 函数的 for 循环,否则它甚至会调用下一个中间件发送响应后。

如果有其他方法可以处理这种情况,请告诉我。

【问题讨论】:

  • 你不需要打破循环。在调用 HandlerFunc 时,循环已经完成了很长时间。直接返回而不调用 h.ServeHTTP。
  • 嗨@Peter 你能解释一下,如果有 3 个中间件,我将如何从中间件 2 发回响应

标签: go middleware


【解决方案1】:

链中的下一个中间件只有在您显式调用它时才会运行。

下一个中间件作为h 传递给您的闭包,您通过调用h.ServeHTTP() 来调用它。如果您不调用它,则不会运行其他中间件,因此您必须提供完整的 HTTP 响应。

【讨论】:

    【解决方案2】:

    Adapt 函数与服务请求无关。它在 HTTP 服务器启动之前执行一次(并且仅一次)。请注意,它返回一个 http.Handler 但它本身不是一个 http.Handler。

    在这种情况下 Adapt 返回的处理程序的行为如下:

    var indexHandler http.Handler
    
    func handlerWithMiddleWare(w http.ResponseWriter, r *http.Request) {
        notify := func(w http.ResponseWriter, r *http.Request) {
                copyMgoSession := func(w http.ResponseWriter, r *http.Request) {
                        checkAuth := func(w http.ResponseWriter, r *http.Request) {
                                addHeader := func(w http.ResponseWriter, r *http.Request) {
                                        indexHandler.ServeHTTP(w, r)
                                }
    
                                addHeader(w, r)
                        }
    
                        checkAuth(w, r)
                }
    
                copyMgoSession(w, r)
        }
    
        notify(w, r)
    }
    

    所以如果你让 CheckAuth 返回而不调用下一个中间件,你可以发送任何你喜欢的响应;就像在任何其他处理程序中一样。

    顺便说一句,您希望让 Adapt 以相反的顺序进行迭代。我不确定您是否知道 Notify 先执行,然后是 CopyMgoSession,然后是 CheckAuth,然后是 AddHeader。

    【讨论】:

      【解决方案3】:

      中间件通常是链式的。有一些框架可以为您做到这一点。一个简洁的例子是Alice

      chain := alice.New(th.Throttle, timeoutHandler, nosurf.NewPure).Then(myHandler)
      

      如果你想自己做,你可以使用递归来避免 for 循环。例如(来自link):

      // buildChain builds the middlware chain recursively, functions are first class
      func buildChain(f http.HandlerFunc, m ...middleware) http.HandlerFunc {
              // if our chain is done, use the original handlerfunc
              if len(m) == 0 {
                      return f
              }
              // otherwise nest the handlerfuncs
              return m[0](buildChain(f, m[1:cap(m)]...))
      }
      

      每个中间件都接收下一个作为参数。因此,下一个必须由前一个处理程序手动调用,否则链会停止。因此,在您的身份验证中间件中,如果身份验证失败并且链停止并且您的错误状态是最后返回的内容,则您不必调用下一个中间件。因此,在您的代码中,您需要接受http.Handler 的参数,这就是下一个处理程序(中间件函数必须具有func(http.Handler) http.Handler 的签名)。有关详细信息,请参阅此blog

      您可能还想设置正确的 http 状态代码。包括这样的内容:

      http.Error(w, "Forbidden: xyz", http.StatusForbidden)
      

      【讨论】:

      • 是的,我有,只是我在这里写的示例代码,即使在发送了 http 状态之后,剩余的中间件也会被for 循环调用。我想以某种方式打破 for 循环
      • 嘿我还是一头雾水,我看不出用递归函数代替for循环有什么区别,你能解释一下控制流程吗
      • gorilla/mux 现在可以链接中间件,所以如果你正在使用它,那么你可以重构以摆脱 alice。
      • 我添加了一句关于当身份验证失败时中间件链如何停止的句子,如果这是你的问题的话。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-12
      • 1970-01-01
      • 2020-01-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多