【问题标题】:Way to pass data up to parent middleware?将数据传递给父中间件的方法?
【发布时间】:2017-02-23 14:10:17
【问题描述】:

我非常清楚如何将数据从处理程序传递到它包装的处理程序,但是有没有一种惯用的方式从包装的处理程序中取回一些东西?这是一个鼓舞人心的例子:我有一个accessLogHandler 和一个authHandleraccessLogHandler 记录每个 http 请求,包括时间和其他请求信息,例如当前登录的用户 ID(如果有)。 authHandler 用于需要登录用户的路由,当用户未登录时为 403。我想用 authHandler 包装我的一些(但可能不是全部)路由,并包装我的所有路由与accessLogHandler。如果用户已登录,我希望我的accessLogHandler 将用户信息与访问日志一起记录。

现在,我想出了一个我不喜欢的解决方案。我将添加代码,然后解释我的一些问题。

// Log the timings of each request optionally including user data
// if there is a logged in user
func accessLogHandler(fn http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        accessLog := newAccessLog()
        ctx := context.WithValue(r.Context(), accessLogKey, accessLog)
        fn.ServeHTTP(w, r.WithContext(ctx))

        // Logs the http access, ommit user info if not set
        accessLog.Log()
    }
}

// pull some junk off the request/cookies/whatever and check if somebody is logged in
func authHandler(fn http.HandlerFunc) http.HandlerFunc {
    return func (w http.ResponseWriter, r *http.Request) {
        //Do some authorization
        user, err := auth(r)
        if err != nil{
            //No userId, don't set anything on the accesslogger
            w.WriteHeader(http.StatusForbiddend)
            return
        }
        //Success a user is logged in, let's make sure the access logger knows
        acessLog := r.Context().Value(accessLogKey).(*AccessLog)
        accessLog.Set("userID", user.ID)
        fn.ServeHTTP(w, r)
    }
}

基本上,我在这里所做的是将accessLog 结构附加到accessLogHandlerauthHandler 内部的上下文中,我正在从上下文中读取accessLog 并调用accessLog.Set 来通知记录用户 ID 存在。

我不喜欢这种方法的一些地方:

  1. context 是不可变的,但我在其上粘贴了一个可变结构并在下游其他地方改变所述结构。感觉像个黑客。
  2. 我的authHandler 现在对accessLog 包具有包级别依赖关系,因为我对*AccessLog 进行了类型断言。
  3. 理想情况下,我的authHandler 可以通过某种方式通知请求堆栈的任何部分有关用户数据的信息,而无需将自身与所述部分紧密耦合。

【问题讨论】:

    标签: go


    【解决方案1】:

    上下文本身是一个接口,因此您可以在 logger 中间件中创建一个新的 logger 上下文,其中包含获得所需行为所需的方法。

    类似这样的:

    type Logger struct{}
    
    func (l *Logger) SetLogField(key string, value interface{}) {// set log field }
    func (l *Logger) Log(){// log request}
    
    type LoggerCtx struct {
        context.Context
        *Logger
    }
    
    func newAccessLog() *Logger {
        return &Logger{}
    }
    
    func accessLogHandler(fn http.HandlerFunc) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
            // create new logger context
            ctx := &LoggerCtx{}
            ctx.Context = r.Context()
            ctx.Logger = newAccessLog()
    
            fn.ServeHTTP(w, r.WithContext(ctx))
    
            // Logs the http access, ommit user info if not set
            ctx.Log()
        }
    }
    
    // pull some junk off the request/cookies/whatever and check if somebody is logged in
    func authHandler(fn http.HandlerFunc) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
            //Do some authorization
            user, err := auth(r)
            if err != nil {
                //No userId, don't set anything on the accesslogger
                w.WriteHeader(http.StatusForbiddend)
                return
            }
    
            //Success a user is logged in, let's make sure the access logger knows
            ctx := r.Context()
    
            // this could be moved - here for clarity
            type setLog interface {
                SetLogField(string, interface{})
            }
    
            if lctx, ok := ctx.(setLog); ok {
                lctx.SetLogField("userID", user.ID)
            }
    
            fn.ServeHTTP(w, r.WithContext(ctx))
        }
    }
    

    【讨论】:

    • 不错!这很好用。使用接口类型断言非常适合删除 dep。不过,有一种情况会崩溃。假设在中间件链中的任何一点我都执行context.WithValue。从链中的那一点开始,我的LoggerCtx 被包装,我无法再访问它。因此,例如,如果我希望链中的另一层向记录器提供一些其他数据,我必须确保在那之前的任何地方都没有使用contxt.WithValue
    • 正确,这就是足枪。您可以保留接口 Idea 并仅使用 WithValue 存储记录器,但现在您的包必须知道密钥和接口。我采取的方法是让我的中间件包提供便利函数来访问存储在上下文中的值,但这对解耦包没有任何作用。
    猜你喜欢
    • 1970-01-01
    • 2021-10-17
    • 1970-01-01
    • 1970-01-01
    • 2019-04-27
    • 2019-07-13
    • 2018-07-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多